/*-------------------------------------------------------------------------
 *
 * parse_clause.c
 *	  处理解析器中的子句
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/parser/parse_clause.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "access/nbtree.h"
#include "access/table.h"
#include "access/tsmapi.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parser.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"


static int	fc_extractRemainingColumns(ParseNamespaceColumn *fc_src_nscolumns,
									List *fc_src_colnames,
									List **fc_src_colnos,
									List **fc_res_colnames, List **fc_res_colvars,
									ParseNamespaceColumn *fc_res_nscolumns);
static Node *fc_transformJoinUsingClause(ParseState *fc_pstate,
									  List *fc_leftVars, List *fc_rightVars);
static Node *fc_transformJoinOnClause(ParseState *fc_pstate, JoinExpr *fc_j,
								   List *fc_namespace);
static ParseNamespaceItem *fc_transformTableEntry(ParseState *fc_pstate, RangeVar *fc_r);
static ParseNamespaceItem *fc_transformRangeSubselect(ParseState *fc_pstate,
												   RangeSubselect *fc_r);
static ParseNamespaceItem *fc_transformRangeFunction(ParseState *fc_pstate,
												  RangeFunction *fc_r);
static ParseNamespaceItem *fc_transformRangeTableFunc(ParseState *fc_pstate,
												   RangeTableFunc *fc_t);
static TableSampleClause *fc_transformRangeTableSample(ParseState *fc_pstate,
													RangeTableSample *fc_rts);
static ParseNamespaceItem *fc_getNSItemForSpecialRelationTypes(ParseState *fc_pstate,
															RangeVar *fc_rv);
static Node *fc_transformFromClauseItem(ParseState *fc_pstate, Node *fc_n,
									 ParseNamespaceItem **fc_top_nsitem,
									 List **fc_namespace);
static Var *fc_buildVarFromNSColumn(ParseNamespaceColumn *fc_nscol);
static Node *fc_buildMergedJoinVar(ParseState *fc_pstate, JoinType fc_jointype,
								Var *fc_l_colvar, Var *fc_r_colvar);
static void fc_setNamespaceColumnVisibility(List *fc_namespace, bool fc_cols_visible);
static void fc_setNamespaceLateralState(List *fc_namespace,
									 bool fc_lateral_only, bool fc_lateral_ok);
static void fc_checkExprIsVarFree(ParseState *fc_pstate, Node *fc_n,
							   const char *fc_constructName);
static TargetEntry *fc_findTargetlistEntrySQL92(ParseState *fc_pstate, Node *fc_node,
											 List **fc_tlist, ParseExprKind fc_exprKind);
static TargetEntry *fc_findTargetlistEntrySQL99(ParseState *fc_pstate, Node *fc_node,
											 List **fc_tlist, ParseExprKind fc_exprKind);
static int	get_matching_location(int fc_sortgroupref,
								  List *fc_sortgrouprefs, List *fc_exprs);
static List *fc_resolve_unique_index_expr(ParseState *fc_pstate, InferClause *fc_infer,
									   Relation fc_heapRel);
static List *fc_addTargetToGroupList(ParseState *fc_pstate, TargetEntry *fc_tle,
								  List *fc_grouplist, List *fc_targetlist, int fc_location);
static WindowClause *fc_findWindowClause(List *fc_wclist, const char *fc_name);
static Node *fc_transformFrameOffset(ParseState *fc_pstate, int fc_frameOptions,
								  Oid fc_rangeopfamily, Oid fc_rangeopcintype, Oid *fc_inRangeFunc,
								  Node *fc_clause);


/*
 * transformFromClause -
 *	  处理FROM子句并将项添加到查询的范围表、连接列表和命名空间中。
 *
 * 注意：我们假设pstate的p_rtable、p_joinlist和p_namespace列表在创建pstate时已初始化为NIL。
 * 我们将添加任何已经存在的条目——这对于规则处理以及UPDATE和DELETE是必需的。
 */
void transformFromClause(ParseState *fc_pstate, List *fc_frmList)
{
	ListCell   *fc_fl;

	/*
	 * 语法将生成一个RangeVars、RangeSubselects、RangeFunctions和/或JoinExprs的列表。
	 * 对每一个进行转换（可能会向rtable中添加条目），检查重复的refnames，然后将其添加到连接列表和命名空间中。
	 *
	 * 注意，我们必须从左到右处理这些项，以便正确处理LATERAL引用。
	 */
	foreach(fc_fl, fc_frmList)
	{
		Node	   *fc_n = lfirst(fc_fl);
		ParseNamespaceItem *fc_nsitem;
		List	   *fc_namespace;

		fc_n = fc_transformFromClauseItem(fc_pstate, fc_n,
									&fc_nsitem,
									&fc_namespace);

		checkNameSpaceConflicts(fc_pstate, fc_pstate->p_namespace, fc_namespace);

		/* 将新的命名空间项标记为仅对LATERAL可见 */
		fc_setNamespaceLateralState(fc_namespace, true, true);

		fc_pstate->p_joinlist = lappend(fc_pstate->p_joinlist, fc_n);
		fc_pstate->p_namespace = list_concat(fc_pstate->p_namespace, fc_namespace);
	}

	/*
	 * 我们已经完成了对FROM列表的解析，因此必须无条件地使所有命名空间项可见。
	 * 注意，这也会重置在我们被调用时已存在的任何命名空间项的lateral_only；
	 * 但这些项应该已经是这样。
	 */
	fc_setNamespaceLateralState(fc_pstate->p_namespace, false, true);
}

/*
 * setTargetTable
 *	  将INSERT/UPDATE/DELETE/MERGE的目标关系添加到范围表中，
 *	  并在ParseState中与之建立特殊链接。
 *
 *	  我们还打开目标关系并对其获取写锁。
 *	  在处理FROM列表之前必须执行此操作，以防目标
 *	  也被提及为源关系——我们希望确保在任何读锁之前获取写锁。
 *
 *	  如果alsoSource为真，将目标添加到查询的连接列表和
 *	  命名空间中。对于INSERT，我们不希望目标被连接到；
 *	  它是元组的目标，而不是来源。MERGE实际上
 *	  是两者，但我们将在连接列表和命名空间中分别添加它，
 *	  因此这里不执行任何操作（如INSERT）是正确的。对于UPDATE/DELETE，
 *	  我们确实需要扫描或连接目标。 （注意：我们不费心
 *	  检查命名空间冲突；我们假设在这些情况下命名空间最初为空。）
 *
 *	  最后，我们将关系标记为需要所指定的权限
 *	  by requiredPerms。
 *
 *	  返回目标关系的rangetable索引。
 */
int setTargetTable(ParseState *fc_pstate, RangeVar *fc_relation,
			   bool fc_inh, bool fc_alsoSource, AclMode fc_requiredPerms)
{
	ParseNamespaceItem *fc_nsitem;

	/*
	 * ENRs隐藏同名表，因此我们需要首先检查它们。
	 * 相比之下，CTEs不会隐藏表（出于这个目的）。
	 */
	if (fc_relation->schemaname == NULL &&
		scanNameSpaceForENR(fc_pstate, fc_relation->relname))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("relation \"%s\" cannot be the target of a modifying statement",
						fc_relation->relname)));

	/* 关闭旧目标；这只能发生在多操作规则中 */
	if (fc_pstate->p_target_relation != NULL)
		table_close(fc_pstate->p_target_relation, NoLock);

	/*
	 * 打开目标关系并抓取适当的锁（我们将在事务结束前保持此锁）。
	 *
	 * free_parsestate()最终会执行相应的table_close()，
	 * 但*不会*释放锁。
	 */
	fc_pstate->p_target_relation = parserOpenTable(fc_pstate, fc_relation,
												RowExclusiveLock);

	/*
	 * 现在构建一个RTE和一个ParseNamespaceItem。
	 */
	fc_nsitem = addRangeTableEntryForRelation(fc_pstate, fc_pstate->p_target_relation,
										   RowExclusiveLock,
										   fc_relation->alias, fc_inh, false);

	/* 记住RTE/nsitem作为查询目标 */
	fc_pstate->p_target_nsitem = fc_nsitem;

	/*
	 * 重写addRangeTableEntry的默认ACL_SELECT权限检查，
	 * 而是将目标表标记为需要确切的指定权限。
	 *
	 * 如果我们在解析分析期间稍后发现对关系的明确引用，
	 * 我们将重新添加ACL_SELECT位；见markVarForSelectPriv及其调用者。
	 */
	fc_nsitem->p_rte->requiredPerms = fc_requiredPerms;

	/*
	 * 如果是UPDATE/DELETE，将表添加到连接列表和命名空间中。
	 */
	if (fc_alsoSource)
		addNSItemToQuery(fc_pstate, fc_nsitem, true, true, true);

	return fc_nsitem->p_rtindex;
}

/*
 * 从源表的列列表中提取所有不在共同列中的列
 *
 * src_nscolumns和src_colnames描述源表。
 *
 * *src_colnos最初包含已经合并列的列号。
 * 我们将每个附加列的列号添加到其中。
 * 还将每个附加列的名称附加到*res_colnames中，
 * 为每个附加列附加一个Var到*res_colvars中，并将列的
 * nscolumns数据复制到res_nscolumns[]中（这个空间由调用者分配，
 * 应当足够大）。
 *
 * 返回添加的列数。
 */
static int fc_extractRemainingColumns(ParseNamespaceColumn *fc_src_nscolumns,
						List *fc_src_colnames,
						List **fc_src_colnos,
						List **fc_res_colnames, List **fc_res_colvars,
						ParseNamespaceColumn *fc_res_nscolumns)
{
	int			fc_colcount = 0;
	Bitmapset  *fc_prevcols;
	int			fc_attnum;
	ListCell   *fc_lc;

	/*
	 * 虽然我们可以在下面的循环中简单地测试"list_member_int(*src_colnos, attnum)"来
	 * 检测已经合并的列，但对于宽输入表来说，这将是O(N^2)。
	 * 相反，构建一个仅仅的合并
	 * USING列的位图集，我们不会在主循环中添加到其中。
	 */
	fc_prevcols = NULL;
	foreach(fc_lc, *fc_src_colnos)
	{
		fc_prevcols = bms_add_member(fc_prevcols, lfirst_int(fc_lc));
	}

	fc_attnum = 0;
	foreach(fc_lc, fc_src_colnames)
	{
		char	   *fc_colname = strVal(lfirst(fc_lc));

		fc_attnum++;
		/* 非删除且未合并？ */
		if (fc_colname[0] != '\0' && !bms_is_member(fc_attnum, fc_prevcols))
		{
			/* 是的，因此将其作为下一个输出列发出 */
			*fc_src_colnos = lappend_int(*fc_src_colnos, fc_attnum);
			*fc_res_colnames = lappend(*fc_res_colnames, lfirst(fc_lc));
			*fc_res_colvars = lappend(*fc_res_colvars,
								   fc_buildVarFromNSColumn(fc_src_nscolumns + fc_attnum - 1));
			/* 复制输入关系对此列的nscolumn数据 */
			fc_res_nscolumns[fc_colcount] = fc_src_nscolumns[fc_attnum - 1];
			fc_colcount++;
		}
	}
	return fc_colcount;
}

/* transformJoinUsingClause()
 *	  从部分转换的USING列表构建完整的ON子句。
 *	  我们给出表示左侧和右侧匹配列的节点列表。
 *	  结果是一个转换的资格表达式。
 */
static Node * fc_transformJoinUsingClause(ParseState *fc_pstate,
						 List *fc_leftVars, List *fc_rightVars)
{
	Node	   *fc_result;
	List	   *fc_andargs = NIL;
	ListCell   *fc_lvars,
			   *fc_rvars;

	
/*
	 * 我们在这里稍微作弊一下，通过构建一个未变换的操作符树
	 * 其叶子节点是已经变换的变量。这需要 transformExpr() 的配合，
	 * 通常情况下我们可以期待它会对已经变换的子节点提出警告。
	 * 然而，这确实意味着我们必须将列标记为需要 SELECT 权限；
	 * transformExpr() 不会处理这个。
	 */
	forboth(fc_lvars, fc_leftVars, fc_rvars, fc_rightVars)
	{
		Var		   *fc_lvar = (Var *) lfirst(fc_lvars);
		Var		   *fc_rvar = (Var *) lfirst(fc_rvars);
		A_Expr	   *fc_e;

		/* 需要读取连接变量的访问权限 */
		markVarForSelectPriv(fc_pstate, fc_lvar);
		markVarForSelectPriv(fc_pstate, fc_rvar);

		/* 现在创建 lvar = rvar 的连接条件 */
		fc_e = makeSimpleA_Expr(AEXPR_OP, "=",
							 (Node *) copyObject(fc_lvar), (Node *) copyObject(fc_rvar),
							 -1);

		/* 如果有多个连接列，准备组合成 AND 子句 */
		fc_andargs = lappend(fc_andargs, fc_e);
	}

	/* 只有在有多个连接列时才需要 AND */
	if (list_length(fc_andargs) == 1)
		fc_result = (Node *) linitial(fc_andargs);
	else
		fc_result = (Node *) makeBoolExpr(AND_EXPR, fc_andargs, -1);

	/*
	 * 由于引用已经是变量，并且肯定来自输入关系，我们不必经历与
	 * transformJoinOnClause() 相同的预处理步骤。只需调用
	 * transformExpr() 来修复操作符，我们就完成了。
	 */
	fc_result = transformExpr(fc_pstate, fc_result, EXPR_KIND_JOIN_USING);

	fc_result = coerce_to_boolean(fc_pstate, fc_result, "JOIN/USING");

	return fc_result;
}

/* transformJoinOnClause()
 *	  转换 JOIN/ON 的条件。
 *	  结果是一个转换后的资格表达式。
 */
static Node * fc_transformJoinOnClause(ParseState *fc_pstate, JoinExpr *fc_j, List *fc_namespace)
{
	Node	   *fc_result;
	List	   *fc_save_namespace;

	/*
	 * 连接表达式应看到的命名空间仅是 JOIN 的两个子树
	 * 加上来自上层 pstate 级别的任何外部引用。临时设置
	 * 此 pstate 的命名空间。我们无需检查 refname 冲突，
	 * 因为 transformFromClauseItem() 已经处理过了。
	 * 所有命名空间项都被标记为可见，无论 LATERAL 状态如何。
	 */
	fc_setNamespaceLateralState(fc_namespace, false, true);

	fc_save_namespace = fc_pstate->p_namespace;
	fc_pstate->p_namespace = fc_namespace;

	fc_result = transformWhereClause(fc_pstate, fc_j->quals,
								  EXPR_KIND_JOIN_ON, "JOIN/ON");

	fc_pstate->p_namespace = fc_save_namespace;

	return fc_result;
}

/*
 * transformTableEntry --- 转换 RangeVar（简单关系引用）
 */
static ParseNamespaceItem * fc_transformTableEntry(ParseState *fc_pstate, RangeVar *fc_r)
{
	/* addRangeTableEntry 执行所有工作 */
	return addRangeTableEntry(fc_pstate, fc_r, fc_r->alias, fc_r->inh, true);
}

/*
 * transformRangeSubselect --- 转换出现在 FROM 中的子 SELECT
 */
static ParseNamespaceItem * fc_transformRangeSubselect(ParseState *fc_pstate, RangeSubselect *fc_r)
{
	Query	   *fc_query;

	/*
	 * 根据 SQL92，我们要求用户为子选择提供别名。为了放宽此限制，
	 * 我们必须准备为未标记的子选择生成一个唯一的别名。
	 * （这仅是 elog，而不是 ereport，因为语法应该已经强制执行了这一点。
	 * 在这里报告错误可能更好，但我们在这里没有良好的错误位置。）
	 */
	if (fc_r->alias == NULL)
		elog(ERROR, "subquery in FROM must have an alias");

	/*
	 * 设置 p_expr_kind 以表明此解析级别正在递归到子选择。
	 * 我们不能嵌套在任何表达式中，所以这里不需要保存-恢复
	 * 逻辑。
	 */
	Assert(fc_pstate->p_expr_kind == EXPR_KIND_NONE);
	fc_pstate->p_expr_kind = EXPR_KIND_FROM_SUBSELECT;

	/*
	 * 如果子选择是 LATERAL，则使此级别的 lateral_only 名称
	 * 对它可见。（LATERAL 不能嵌套在单一的 pstate 级别中，
	 * 所以我们不需要保存/恢复逻辑。）
	 */
	Assert(!fc_pstate->p_lateral_active);
	fc_pstate->p_lateral_active = fc_r->lateral;

	/*
	 * 分析和转换子查询。
	 */
	fc_query = parse_sub_analyze(fc_r->subquery, fc_pstate, NULL,
							  isLockedRefname(fc_pstate, fc_r->alias->aliasname),
							  true);

	/* 恢复状态 */
	fc_pstate->p_lateral_active = false;
	fc_pstate->p_expr_kind = EXPR_KIND_NONE;

	/*
	 * 检查我们得到了一个 SELECT。如果文法的限制条件给
	 * 其它情况是不可能的，但还是要检查一下。
	 */
	if (!IsA(fc_query, Query) ||
		fc_query->commandType != CMD_SELECT)
		elog(ERROR, "unexpected non-SELECT command in subquery in FROM");

	/*
	 * 好吧，为子查询构建一个 RTE 和 nsitem。
	 */
	return addRangeTableEntryForSubquery(fc_pstate,
										 fc_query,
										 fc_r->alias,
										 fc_r->lateral,
										 true);
}


/*
 * transformRangeFunction --- 转换在 FROM 中出现的函数调用
 */
static ParseNamespaceItem * fc_transformRangeFunction(ParseState *fc_pstate, RangeFunction *fc_r)
{
	List	   *fc_funcexprs = NIL;
	List	   *fc_funcnames = NIL;
	List	   *fc_coldeflists = NIL;
	bool		fc_is_lateral;
	ListCell   *fc_lc;

	/*
	 * 无论 RangeFunction 是否被显式标记为 LATERAL，我们都会
	 * 使这一层级的 lateral_only 名称可见。这对于 SQL
	 * 规范合规是必需的，并且对于 FROM 中的所有函数以便利性理由
	 * 似乎也是有用的。
	 *
	 * （LATERAL 不能嵌套在单一 pstate 级别中，所以我们不需要
	 * 保存/恢复逻辑。）
	 */
	Assert(!fc_pstate->p_lateral_active);
	fc_pstate->p_lateral_active = true;

	/*
	 * 转换原始表达式。
	 *
	 * 在转换期间，还保存函数名称以备将来作为别名
	 * 和列名称使用。我们使用与 SELECT 输出表达式相同的
	 * 转换规则。对于 FuncCall 节点，结果将是
	 * 函数名称，但文法有可能返回其他节点类型。
	 *
	 * 我们必须立即获取这些信息，因为 FigureColname 只能
	 * 在原始解析树上工作。实际上如何处理名称留给
	 * addRangeTableEntryForFunction。
	 *
	 * 同样，如果有列定义列表，也要收集。如果我们在这里发现一个
	 * 而 RangeFunction 也有一个，则要提出抱怨。
	 */
	foreach(fc_lc, fc_r->functions)
	{
		List	   *fc_pair = (List *) lfirst(fc_lc);
		Node	   *fc_fexpr;
		List	   *fc_coldeflist;
		Node	   *fc_newfexpr;
		Node	   *fc_last_srf;

		/* 拆解函数调用/列定义列表对 */
		Assert(list_length(fc_pair) == 2);
		fc_fexpr = (Node *) linitial(fc_pair);
		fc_coldeflist = (List *) lsecond(fc_pair);

		/*
		 * 如果我们发现一个 no-arg 的 unnest() 函数调用并且
		 * 没有特殊装饰，将其转换为针对每个参数的单独 unnest() 调用。
		 * 这确实是一种变通办法，但比其他实现 SQL 标准 UNNEST()
		 * 语法的方式要好。
		 *
		 * 如果有任何装饰（包括 coldeflist），我们将不进行转换，
		 * 这可能会导致后续出现 no-such-function 错误。我们本来
		 * 可以立即抛出错误，但这似乎并没有特别有帮助。如果有人使用
		 * 任何此类装饰，那么他们并不是在使用 SQL 标准语法，更可能
		 * 期待一个未调整的函数调用。
		 *
		 * 注意：转换将一个非模式限定的 unnest() 函数名称转换为
		 * 模式限定的 pg_catalog.unnest()。这一选择也是有争议的，
		 * 但在进行此转换时强制使用内置 unnest() 似乎是合理的。
		 */
		if (IsA(fc_fexpr, FuncCall))
		{
			FuncCall   *fc_fc = (FuncCall *) fc_fexpr;

			if (list_length(fc_fc->funcname) == 1 &&
				strcmp(strVal(linitial(fc_fc->funcname)), "unnest") == 0 &&
				list_length(fc_fc->args) > 1 &&
				fc_fc->agg_order == NIL &&
				fc_fc->agg_filter == NULL &&
				fc_fc->over == NULL &&
				!fc_fc->agg_star &&
				!fc_fc->agg_distinct &&
				!fc_fc->func_variadic &&
				fc_coldeflist == NIL)
			{
				ListCell   *fc_lc;

				foreach(fc_lc, fc_fc->args)
				{
					Node	   *fc_arg = (Node *) lfirst(fc_lc);
					FuncCall   *fc_newfc;

					fc_last_srf = fc_pstate->p_last_srf;

					fc_newfc = makeFuncCall(SystemFuncName("unnest"),
										 list_make1(fc_arg),
										 COERCE_EXPLICIT_CALL,
										 fc_fc->location);

					fc_newfexpr = transformExpr(fc_pstate, (Node *) fc_newfc,
											 EXPR_KIND_FROM_FUNCTION);

					/* nodeFunctionscan.c 要求 SRFs 处于顶层 */
					if (fc_pstate->p_last_srf != fc_last_srf &&
						fc_pstate->p_last_srf != fc_newfexpr)
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("set-returning functions must appear at top level of FROM"),
								 parser_errposition(fc_pstate,
													exprLocation(fc_pstate->p_last_srf))));

					fc_funcexprs = lappend(fc_funcexprs, fc_newfexpr);

					fc_funcnames = lappend(fc_funcnames,
										FigureColname((Node *) fc_newfc));

					/* coldeflist 为空，因此不可能有错误 */

					fc_coldeflists = lappend(fc_coldeflists, fc_coldeflist);
				}
				continue;		/* 完成此函数项 */
			}
		}

		/* 正常情况... */
		fc_last_srf = fc_pstate->p_last_srf;

		fc_newfexpr = transformExpr(fc_pstate, fc_fexpr,
								 EXPR_KIND_FROM_FUNCTION);

		/* nodeFunctionscan.c 要求 SRFs 处于顶层 */
		if (fc_pstate->p_last_srf != fc_last_srf &&
			fc_pstate->p_last_srf != fc_newfexpr)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("set-returning functions must appear at top level of FROM"),
					 parser_errposition(fc_pstate,
										exprLocation(fc_pstate->p_last_srf))));

		fc_funcexprs = lappend(fc_funcexprs, fc_newfexpr);

		fc_funcnames = lappend(fc_funcnames,
							FigureColname(fc_fexpr));

		if (fc_coldeflist && fc_r->coldeflist)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("multiple column definition lists are not allowed for the same function"),
					 parser_errposition(fc_pstate,
										exprLocation((Node *) fc_r->coldeflist))));

		fc_coldeflists = lappend(fc_coldeflists, fc_coldeflist);
	}

	fc_pstate->p_lateral_active = false;

	/*
	 * 我们现在必须分配排序规则，以便 RTE 能够暴露正确的排序规则
	 * 信息以供从中创建的 Vars 使用。
	 */
	assign_list_collations(fc_pstate, fc_funcexprs);

	/*
	 * 如果有一个 coldeflist，安装顶层 coldeflist（我们已经检查过
	 * 没有冲突的每个函数的 coldeflist）。
	 *
	 * 我们只允许在存在单个函数（即使在 UNNEST 展开后）且没有
	 * WITH ORDINALITY 的情况下这样做。后者限制的原因是
	 * 目前尚不清楚序号列是否应在 coldeflist 中，用户在某个
	 * 方向上犯错误的可能性太大。在这种情况下，将 coldeflist 
	 * 放在 ROWS FROM() 内部更加清晰。
	 */
	if (fc_r->coldeflist)
	{
		if (list_length(fc_funcexprs) != 1)
		{
			if (fc_r->is_rowsfrom)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("ROWS FROM() with multiple functions cannot have a column definition list"),
						 errhint("Put a separate column definition list for each function inside ROWS FROM()."),
						 parser_errposition(fc_pstate,
											exprLocation((Node *) fc_r->coldeflist))));
			else
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("UNNEST() with multiple arguments cannot have a column definition list"),
						 errhint("Use separate UNNEST() calls inside ROWS FROM(), and attach a column definition list to each one."),
						 parser_errposition(fc_pstate,
											exprLocation((Node *) fc_r->coldeflist))));
		}
		if (fc_r->ordinality)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("WITH ORDINALITY cannot be used with a column definition list"),
					 errhint("Put the column definition list inside ROWS FROM()."),
					 parser_errposition(fc_pstate,
										exprLocation((Node *) fc_r->coldeflist))));

		fc_coldeflists = list_make1(fc_r->coldeflist);
	}

	/*
	 * 如果用户显式指定了 LATERAL，或者其中存在任何 lateral 
	 * 交叉引用，则将 RTE 标记为 LATERAL。
	 */
	fc_is_lateral = fc_r->lateral || contain_vars_of_level((Node *) fc_funcexprs, 0);

	/*
	 * 好吧，为函数构建一个 RTE 和 nsitem。
	 */
	return addRangeTableEntryForFunction(fc_pstate,
										 fc_funcnames, fc_funcexprs, fc_coldeflists,
										 fc_r, fc_is_lateral, true);
}

/*
 * transformRangeTableFunc -
 *			将原始 RangeTableFunc 转换为 TableFunc。
 *
 * 转换命名空间子句、文档生成表达式、行生成表达式、列生成
 * 表达式及默认值表达式。
 */
static ParseNamespaceItem * fc_transformRangeTableFunc(ParseState *fc_pstate, RangeTableFunc *fc_rtf)
{
	TableFunc  *fc_tf = makeNode(TableFunc);
	const char *fc_constructName;
	Oid			fc_docType;
	bool		fc_is_lateral;
	ListCell   *fc_col;
	char	  **fc_names;
	int			fc_colno;

	/* 当前仅支持 XMLTABLE */
	fc_constructName = "XMLTABLE";
	fc_docType = XMLOID;

	/*
	 * 无论 RangeTableFunc 是否被显式标记为 LATERAL，我们都会
	 * 使这一层级的 lateral_only 名称可见。这对于 SQL
	 * 规范合规是必需的，并且对于 FROM 中的所有函数以便利性理由
	 * 似乎也是有用的。
	 *
	 * （LATERAL 不能嵌套在单一 pstate 级别中，所以我们不需要
	 * 保存/恢复逻辑。）
	 */
	Assert(!fc_pstate->p_lateral_active);
	fc_pstate->p_lateral_active = true;

	/* 转换并应用类型转换到行生成表达式... */
	Assert(fc_rtf->rowexpr != NULL);
	fc_tf->rowexpr = coerce_to_specific_type(fc_pstate,
										  transformExpr(fc_pstate, fc_rtf->rowexpr, EXPR_KIND_FROM_FUNCTION),
										  TEXTOID,
										  fc_constructName);
	assign_expr_collations(fc_pstate, fc_tf->rowexpr);

	/* ...并对文档本身进行处理 */
	Assert(fc_rtf->docexpr != NULL);
	fc_tf->docexpr = coerce_to_specific_type(fc_pstate,
										  transformExpr(fc_pstate, fc_rtf->docexpr, EXPR_KIND_FROM_FUNCTION),
										  fc_docType,
										  fc_constructName);
	assign_expr_collations(fc_pstate, fc_tf->docexpr);

	/* 未定义的序号列号 */
	fc_tf->ordinalitycol = -1;

	/* 处理列规格 */
	fc_names = palloc(sizeof(char *) * list_length(fc_rtf->columns));

	fc_colno = 0;
	foreach(fc_col, fc_rtf->columns)
	{
		RangeTableFuncCol *fc_rawc = (RangeTableFuncCol *) lfirst(fc_col);
		Oid			fc_typid;
		int32		fc_typmod;
		Node	   *fc_colexpr;
		Node	   *fc_coldefexpr;
		int			fc_j;

		fc_tf->colnames = lappend(fc_tf->colnames,
							   makeString(pstrdup(fc_rawc->colname)));

		
/*
		 * 确定新列的类型和 typmod。对于 ORDINALITY 列，按照规范是 INTEGER；其他的由用户指定。
		 */
		if (fc_rawc->for_ordinality)
		{
			if (fc_tf->ordinalitycol != -1)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("only one FOR ORDINALITY column is allowed"),
						 parser_errposition(fc_pstate, fc_rawc->location)));

			fc_typid = INT4OID;
			fc_typmod = -1;
			fc_tf->ordinalitycol = fc_colno;
		}
		else
		{
			if (fc_rawc->typeName->setof)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
						 errmsg("column \"%s\" cannot be declared SETOF",
								fc_rawc->colname),
						 parser_errposition(fc_pstate, fc_rawc->location)));

			typenameTypeIdAndMod(fc_pstate, fc_rawc->typeName,
								 &fc_typid, &fc_typmod);
		}

		fc_tf->coltypes = lappend_oid(fc_tf->coltypes, fc_typid);
		fc_tf->coltypmods = lappend_int(fc_tf->coltypmods, fc_typmod);
		fc_tf->colcollations = lappend_oid(fc_tf->colcollations,
										get_typcollation(fc_typid));

		/* 转换 PATH 和 DEFAULT 表达式 */
		if (fc_rawc->colexpr)
		{
			fc_colexpr = coerce_to_specific_type(fc_pstate,
											  transformExpr(fc_pstate, fc_rawc->colexpr,
															EXPR_KIND_FROM_FUNCTION),
											  TEXTOID,
											  fc_constructName);
			assign_expr_collations(fc_pstate, fc_colexpr);
		}
		else
			fc_colexpr = NULL;

		if (fc_rawc->coldefexpr)
		{
			fc_coldefexpr = coerce_to_specific_type_typmod(fc_pstate,
														transformExpr(fc_pstate, fc_rawc->coldefexpr,
																	  EXPR_KIND_FROM_FUNCTION),
														fc_typid, fc_typmod,
														fc_constructName);
			assign_expr_collations(fc_pstate, fc_coldefexpr);
		}
		else
			fc_coldefexpr = NULL;

		fc_tf->colexprs = lappend(fc_tf->colexprs, fc_colexpr);
		fc_tf->coldefexprs = lappend(fc_tf->coldefexprs, fc_coldefexpr);

		if (fc_rawc->is_not_null)
			fc_tf->notnulls = bms_add_member(fc_tf->notnulls, fc_colno);

		/* 确保列名称是唯一的 */
		for (fc_j = 0; fc_j < fc_colno; fc_j++)
			if (strcmp(fc_names[fc_j], fc_rawc->colname) == 0)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("column name \"%s\" is not unique",
								fc_rawc->colname),
						 parser_errposition(fc_pstate, fc_rawc->location)));
		fc_names[fc_colno] = fc_rawc->colname;

		fc_colno++;
	}
	pfree(fc_names);

	/* 名称空间（如果有的话）也需要转换 */
	if (fc_rtf->namespaces != NIL)
	{
		ListCell   *fc_ns;
		ListCell   *fc_lc2;
		List	   *fc_ns_uris = NIL;
		List	   *fc_ns_names = NIL;
		bool		fc_default_ns_seen = false;

		foreach(fc_ns, fc_rtf->namespaces)
		{
			ResTarget  *fc_r = (ResTarget *) lfirst(fc_ns);
			Node	   *fc_ns_uri;

			Assert(IsA(fc_r, ResTarget));
			fc_ns_uri = transformExpr(fc_pstate, fc_r->val, EXPR_KIND_FROM_FUNCTION);
			fc_ns_uri = coerce_to_specific_type(fc_pstate, fc_ns_uri,
											 TEXTOID, fc_constructName);
			assign_expr_collations(fc_pstate, fc_ns_uri);
			fc_ns_uris = lappend(fc_ns_uris, fc_ns_uri);

			/* 验证名称列表的一致性：没有重复项，仅一个 DEFAULT */
			if (fc_r->name != NULL)
			{
				foreach(fc_lc2, fc_ns_names)
				{
					String	   *fc_ns_node = lfirst_node(String, fc_lc2);

					if (fc_ns_node == NULL)
						continue;
					if (strcmp(strVal(fc_ns_node), fc_r->name) == 0)
						ereport(ERROR,
								(errcode(ERRCODE_SYNTAX_ERROR),
								 errmsg("namespace name \"%s\" is not unique",
										fc_r->name),
								 parser_errposition(fc_pstate, fc_r->location)));
				}
			}
			else
			{
				if (fc_default_ns_seen)
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("only one default namespace is allowed"),
							 parser_errposition(fc_pstate, fc_r->location)));
				fc_default_ns_seen = true;
			}

			/* 我们通过空指针表示 DEFAULT */
			fc_ns_names = lappend(fc_ns_names,
							   fc_r->name ? makeString(fc_r->name) : NULL);
		}

		fc_tf->ns_uris = fc_ns_uris;
		fc_tf->ns_names = fc_ns_names;
	}

	fc_tf->location = fc_rtf->location;

	fc_pstate->p_lateral_active = false;

	/*
	 * 如果用户显式指定了 LATERAL，或者其中存在任何 lateral 
	 * 交叉引用，则将 RTE 标记为 LATERAL。
	 */
	fc_is_lateral = fc_rtf->lateral || contain_vars_of_level((Node *) fc_tf, 0);

	return addRangeTableEntryForTableFunc(fc_pstate,
										  fc_tf, fc_rtf->alias, fc_is_lateral, true);
}

/*
 * transformRangeTableSample --- 转换 TABLESAMPLE 子句
 *
 * 调用者已经转换了 rts->relation，我们只需验证
 * 剩余字段并创建一个 TableSampleClause 节点。
 */
static TableSampleClause * fc_transformRangeTableSample(ParseState *fc_pstate, RangeTableSample *fc_rts)
{
	TableSampleClause *fc_tablesample;
	Oid			fc_handlerOid;
	Oid			fc_funcargtypes[1];
	TsmRoutine *fc_tsm;
	List	   *fc_fargs;
	ListCell   *fc_larg,
			   *fc_ltyp;

	/*
	 * 要验证样本方法名称，请查找具有相同名称的处理函数，
	 * 该函数有一个虚拟的 INTERNAL 参数和一个结果类型为
	 * tsm_handler。 （注意：在 SQL 标准中，tablesample 方法名称未经过 schema 限定；
	 * 但由于对我们来说它们只是函数，因此我们允许
	 * schema 限定以解决任何潜在的歧义。）
	 */
	fc_funcargtypes[0] = INTERNALOID;

	fc_handlerOid = LookupFuncName(fc_rts->method, 1, fc_funcargtypes, true);

	/* 我们希望错误报告无此方法，而不是无此函数 */
	if (!OidIsValid(fc_handlerOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("tablesample method %s does not exist",
						NameListToString(fc_rts->method)),
				 parser_errposition(fc_pstate, fc_rts->location)));

	/* 检查处理程序具有正确的返回类型 */
	if (get_func_rettype(fc_handlerOid) != TSM_HANDLEROID)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("function %s must return type %s",
						NameListToString(fc_rts->method), "tsm_handler"),
				 parser_errposition(fc_pstate, fc_rts->location)));

	/* 好吧，运行处理程序以获取 TsmRoutine，以获取参数类型信息 */
	fc_tsm = GetTsmRoutine(fc_handlerOid);

	fc_tablesample = makeNode(TableSampleClause);
	fc_tablesample->tsmhandler = fc_handlerOid;

	/* 检查用户提供的参数数量是否符合预期 */
	if (list_length(fc_rts->args) != list_length(fc_tsm->parameterTypes))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLESAMPLE_ARGUMENT),
				 errmsg_plural("tablesample method %s requires %d argument, not %d",
							   "tablesample method %s requires %d arguments, not %d",
							   list_length(fc_tsm->parameterTypes),
							   NameListToString(fc_rts->method),
							   list_length(fc_tsm->parameterTypes),
							   list_length(fc_rts->args)),
				 parser_errposition(fc_pstate, fc_rts->location)));

	/*
	 * 转换参数，根据需要进行类型转换。 请注意，我们还必须
	 * 现在分配排序规则，因为 assign_query_collations() 不会
	 * 检查 RTE 的任何子结构。
	 */
	fc_fargs = NIL;
	forboth(fc_larg, fc_rts->args, fc_ltyp, fc_tsm->parameterTypes)
	{
		Node	   *fc_arg = (Node *) lfirst(fc_larg);
		Oid			fc_argtype = lfirst_oid(fc_ltyp);

		fc_arg = transformExpr(fc_pstate, fc_arg, EXPR_KIND_FROM_FUNCTION);
		fc_arg = coerce_to_specific_type(fc_pstate, fc_arg, fc_argtype, "TABLESAMPLE");
		assign_expr_collations(fc_pstate, fc_arg);
		fc_fargs = lappend(fc_fargs, fc_arg);
	}
	fc_tablesample->args = fc_fargs;

	/* 处理 REPEATABLE（种子） */
	if (fc_rts->repeatable != NULL)
	{
		Node	   *fc_arg;

		if (!fc_tsm->repeatable_across_queries)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("tablesample method %s does not support REPEATABLE",
							NameListToString(fc_rts->method)),
					 parser_errposition(fc_pstate, fc_rts->location)));

		fc_arg = transformExpr(fc_pstate, fc_rts->repeatable, EXPR_KIND_FROM_FUNCTION);
		fc_arg = coerce_to_specific_type(fc_pstate, fc_arg, FLOAT8OID, "REPEATABLE");
		assign_expr_collations(fc_pstate, fc_arg);
		fc_tablesample->repeatable = (Expr *) fc_arg;
	}
	else
		fc_tablesample->repeatable = NULL;

	return fc_tablesample;
}

/*
 * getNSItemForSpecialRelationTypes
 *
 * 如果给定的 RangeVar 指向 CTE 或 EphemeralNamedRelation，
 * 构建并返回一个适当的 ParseNamespaceItem，否则返回 NULL
 */
static ParseNamespaceItem * fc_getNSItemForSpecialRelationTypes(ParseState *fc_pstate, RangeVar *fc_rv)
{
	ParseNamespaceItem *fc_nsitem;
	CommonTableExpr *fc_cte;
	Index		fc_levelsup;

	/*
	 * 如果它是一个合格的名称，则它不能是 CTE 或元组存储引用
	 */
	if (fc_rv->schemaname)
		return NULL;

	fc_cte = scanNameSpaceForCTE(fc_pstate, fc_rv->relname, &fc_levelsup);
	if (fc_cte)
		fc_nsitem = addRangeTableEntryForCTE(fc_pstate, fc_cte, fc_levelsup, fc_rv, true);
	else if (scanNameSpaceForENR(fc_pstate, fc_rv->relname))
		fc_nsitem = addRangeTableEntryForENR(fc_pstate, fc_rv, true);
	else
		fc_nsitem = NULL;

	return fc_nsitem;
}

/*
 * transformFromClauseItem -
 *	  转换 FROM 子句项，将任何必需的条目添加到
 *	  正在 ParseState 中构建的范围表列表中，并返回
 *	  转换后的项以便包含在连接列表中。 还构建一个
 *	  ParseNamespaceItem 列表，描述该项所暴露的名称。
 *	  此例程可以递归以处理 SQL92 JOIN 表达式。
 *
 * 函数的返回值是要添加到联合树的节点（即
 * RangeTblRef 或 JoinExpr）。 其他输出参数包括：
 *
 * *top_nsitem: 接收直接对应于
 * 联合树项目的 ParseNamespaceItem。 （这仅在内部递归期间使用，而不用于外部调用者。）
 *
 * *namespace: 接收一个 ParseNamespaceItems 列表，代表
 * 作为此项的表/列名称暴露的 RTE。 （这些项中的 lateral_only 标志
 * 是不确定的，调用者在使用前应显式设置。）
 */
static Node * fc_transformFromClauseItem(ParseState *fc_pstate, Node *fc_n,
						ParseNamespaceItem **fc_top_nsitem,
						List **fc_namespace)
{
	/* 防止由于过深的子树导致的栈溢出 */
	check_stack_depth();

	if (IsA(fc_n, RangeVar))
	{
		/* 普通关系引用，或可能是 CTE 引用 */
		RangeVar   *fc_rv = (RangeVar *) fc_n;
		RangeTblRef *fc_rtr;
		ParseNamespaceItem *fc_nsitem;

		/* 检查这是否是 CTE 或元组存储引用 */
		fc_nsitem = fc_getNSItemForSpecialRelationTypes(fc_pstate, fc_rv);

		/* 如果上面未找到，则必须是表引用 */
		if (!fc_nsitem)
			fc_nsitem = fc_transformTableEntry(fc_pstate, fc_rv);

		*fc_top_nsitem = fc_nsitem;
		*fc_namespace = list_make1(fc_nsitem);
		fc_rtr = makeNode(RangeTblRef);
		fc_rtr->rtindex = fc_nsitem->p_rtindex;
		return (Node *) fc_rtr;
	}
	else if (IsA(fc_n, RangeSubselect))
	{
		/* 子 SELECT 像一个普通关系 */
		RangeTblRef *fc_rtr;
		ParseNamespaceItem *fc_nsitem;

		fc_nsitem = fc_transformRangeSubselect(fc_pstate, (RangeSubselect *) fc_n);
		*fc_top_nsitem = fc_nsitem;
		*fc_namespace = list_make1(fc_nsitem);
		fc_rtr = makeNode(RangeTblRef);
		fc_rtr->rtindex = fc_nsitem->p_rtindex;
		return (Node *) fc_rtr;
	}
	else if (IsA(fc_n, RangeFunction))
	{
		/* 函数像一个普通关系 */
		RangeTblRef *fc_rtr;
		ParseNamespaceItem *fc_nsitem;

		fc_nsitem = fc_transformRangeFunction(fc_pstate, (RangeFunction *) fc_n);
		*fc_top_nsitem = fc_nsitem;
		*fc_namespace = list_make1(fc_nsitem);
		fc_rtr = makeNode(RangeTblRef);
		fc_rtr->rtindex = fc_nsitem->p_rtindex;
		return (Node *) fc_rtr;
	}
	else if (IsA(fc_n, RangeTableFunc))
	{
		/* 表函数像一个普通关系 */
		RangeTblRef *fc_rtr;
		ParseNamespaceItem *fc_nsitem;

		fc_nsitem = fc_transformRangeTableFunc(fc_pstate, (RangeTableFunc *) fc_n);
		*fc_top_nsitem = fc_nsitem;
		*fc_namespace = list_make1(fc_nsitem);
		fc_rtr = makeNode(RangeTblRef);
		fc_rtr->rtindex = fc_nsitem->p_rtindex;
		return (Node *) fc_rtr;
	}
	else if (IsA(fc_n, RangeTableSample))
	{
		/* TABLESAMPLE 子句（包装一些其他有效的 FROM 节点） */
		RangeTableSample *fc_rts = (RangeTableSample *) fc_n;
		Node	   *fc_rel;
		RangeTblEntry *fc_rte;

		/* 递归转换包含的关系 */
		fc_rel = fc_transformFromClauseItem(fc_pstate, fc_rts->relation,
									  fc_top_nsitem, fc_namespace);
		fc_rte = (*fc_top_nsitem)->p_rte;
		/* 我们仅在普通关系和物化视图上支持此操作 */
		if (fc_rte->rtekind != RTE_RELATION ||
			(fc_rte->relkind != RELKIND_RELATION &&
			 fc_rte->relkind != RELKIND_MATVIEW &&
			 fc_rte->relkind != RELKIND_PARTITIONED_TABLE))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("TABLESAMPLE clause can only be applied to tables and materialized views"),
					 parser_errposition(fc_pstate, exprLocation(fc_rts->relation))));

		/* 转换 TABLESAMPLE 细节并附加到 RTE */
		fc_rte->tablesample = fc_transformRangeTableSample(fc_pstate, fc_rts);
		return fc_rel;
	}
	else if (IsA(fc_n, JoinExpr))
	{
		/* 一种新式的连接表达式 */
		JoinExpr   *fc_j = (JoinExpr *) fc_n;
		ParseNamespaceItem *fc_nsitem;
		ParseNamespaceItem *fc_l_nsitem;
		ParseNamespaceItem *fc_r_nsitem;
		List	   *fc_l_namespace,
				   *fc_r_namespace,
				   *fc_my_namespace,
				   *fc_l_colnames,
				   *fc_r_colnames,
				   *fc_res_colnames,
				   *fc_l_colnos,
				   *fc_r_colnos,
				   *fc_res_colvars;
		ParseNamespaceColumn *fc_l_nscolumns,
				   *fc_r_nscolumns,
				   *fc_res_nscolumns;
		int			fc_res_colindex;
		bool		fc_lateral_ok;
		int			fc_sv_namespace_length;
		int			fc_k;

		/*
		 * 递归处理左子树，然后是右子树。 我们必须以此顺序进行
		 * 以正确访问 LATERAL 引用。
		 */
		fc_j->larg = fc_transformFromClauseItem(fc_pstate, fc_j->larg,
										  &fc_l_nsitem,
										  &fc_l_namespace);

		/*
		 * 通过临时将左侧 RTE 可用于 LATERAL 访问
		 * 在右侧，方法是将它们添加到 pstate 的命名空间
		 * 列表中。 根据 SQL:2008，如果连接类型不是 INNER 或 LEFT，
		 * 则左侧名称仍必须暴露，但引用它们是错误的。
		 * （愚蠢的设计，但就是这样。）因此，
		 * 我们始终将它们推入命名空间，但在连接类型错误时标记它们为
		 * non-lateral_ok。
		 *
		 * 请注意，我们不要求合并的命名空间列表是
		 * 无冲突的。 请参阅 scanNameSpaceForRefname() 的注释。
		 */
		fc_lateral_ok = (fc_j->jointype == JOIN_INNER || fc_j->jointype == JOIN_LEFT);
		fc_setNamespaceLateralState(fc_l_namespace, true, fc_lateral_ok);

		fc_sv_namespace_length = list_length(fc_pstate->p_namespace);
		fc_pstate->p_namespace = list_concat(fc_pstate->p_namespace, fc_l_namespace);

		/* 现在我们可以处理 RHS */
		fc_j->rarg = fc_transformFromClauseItem(fc_pstate, fc_j->rarg,
										  &fc_r_nsitem,
										  &fc_r_namespace);

		/* 再次从命名空间列表中移除左侧 RTE */
		fc_pstate->p_namespace = list_truncate(fc_pstate->p_namespace,
											fc_sv_namespace_length);

		/*
		 * 检查左侧和右侧子树中的冲突 refnames。必须做到这一点，
		 * 因为更高层级将假定我返回的是一个自我
		 * 一致的命名空间列表。
		 */
		checkNameSpaceConflicts(fc_pstate, fc_l_namespace, fc_r_namespace);

		/*
		 * 生成可用于以下可能性组合的命名空间信息。
		 */
		fc_my_namespace = list_concat(fc_l_namespace, fc_r_namespace);

		/*
		 * 我们将从 nscolumns 数据和 eref 别名列名开始
		 * 每个输入 nsitems。请注意，这些列包括已删除的
		 * 列，这很有帮助，因为我们可以更容易地跟踪物理
		 * 输入列号。
		 */
		fc_l_nscolumns = fc_l_nsitem->p_nscolumns;
		fc_l_colnames = fc_l_nsitem->p_names->colnames;
		fc_r_nscolumns = fc_r_nsitem->p_nscolumns;
		fc_r_colnames = fc_r_nsitem->p_names->colnames;

		/*
		 * 自然连接未明确指定列；必须生成
		 * 要连接的列。需要遍历每个
		 * 表或连接结果中的列列表，并匹配列名。
		 * 使用第一个表，检查第二个表中的每个列是否匹配。
		 * （我们稍后将检查匹配是否唯一。）此步骤的结果是一个
		 * 列名列表，就像一个明确写的
		 * USING 列表一样。
		 */
		if (fc_j->isNatural)
		{
			List	   *fc_rlist = NIL;
			ListCell   *fc_lx,
					   *fc_rx;

			Assert(fc_j->usingClause == NIL);	/* 不应该有 USING() */

			foreach(fc_lx, fc_l_colnames)
			{
				char	   *fc_l_colname = strVal(lfirst(fc_lx));
				String	   *fc_m_name = NULL;

				if (fc_l_colname[0] == '\0')
					continue;	/* 忽略已删除的列 */

				foreach(fc_rx, fc_r_colnames)
				{
					char	   *fc_r_colname = strVal(lfirst(fc_rx));

					if (strcmp(fc_l_colname, fc_r_colname) == 0)
					{
						fc_m_name = makeString(fc_l_colname);
						break;
					}
				}

				/* 匹配了右侧列？那么保留为连接列... */
				if (fc_m_name != NULL)
					fc_rlist = lappend(fc_rlist, fc_m_name);
			}

			fc_j->usingClause = fc_rlist;
		}

		/*
		 * 如果指定了 USING 子句别名，请将 USING 列保存为
		 * 其列列表。
		 */
		if (fc_j->join_using_alias)
			fc_j->join_using_alias->colnames = fc_j->usingClause;

		/*
		 * 现在如果有连接条件，将其转换。
		 */
		fc_l_colnos = NIL;
		fc_r_colnos = NIL;
		fc_res_colnames = NIL;
		fc_res_colvars = NIL;

		/* 这可能比需要的更大，但没有必要精确 */
		fc_res_nscolumns = (ParseNamespaceColumn *)
			palloc0((list_length(fc_l_colnames) + list_length(fc_r_colnames)) *
					sizeof(ParseNamespaceColumn));
		fc_res_colindex = 0;

		if (fc_j->usingClause)
		{
			/*
			 * JOIN/USING（或上面转换的 NATURAL JOIN）。
			 * 将列表转换为显式的 ON 条件，并生成
			 * 合并结果列的列表。
			 */
			List	   *fc_ucols = fc_j->usingClause;
			List	   *fc_l_usingvars = NIL;
			List	   *fc_r_usingvars = NIL;
			ListCell   *fc_ucol;

			Assert(fc_j->quals == NULL);	/* 不应该有 ON() */

			foreach(fc_ucol, fc_ucols)
			{
				char	   *fc_u_colname = strVal(lfirst(fc_ucol));
				ListCell   *fc_col;
				int			fc_ndx;
				int			fc_l_index = -1;
				int			fc_r_index = -1;
				Var		   *fc_l_colvar,
						   *fc_r_colvar;
				Node	   *fc_u_colvar;
				ParseNamespaceColumn *fc_res_nscolumn;

				Assert(fc_u_colname[0] != '\0');

				/* 检查 USING(foo,foo) */
				foreach(fc_col, fc_res_colnames)
				{
					char	   *fc_res_colname = strVal(lfirst(fc_col));

					if (strcmp(fc_res_colname, fc_u_colname) == 0)
						ereport(ERROR,
								(errcode(ERRCODE_DUPLICATE_COLUMN),
								 errmsg("column name \"%s\" appears more than once in USING clause",
										fc_u_colname)));
				}

				/* 在左输入中查找 */
				fc_ndx = 0;
				foreach(fc_col, fc_l_colnames)
				{
					char	   *fc_l_colname = strVal(lfirst(fc_col));

					if (strcmp(fc_l_colname, fc_u_colname) == 0)
					{
						if (fc_l_index >= 0)
							ereport(ERROR,
									(errcode(ERRCODE_AMBIGUOUS_COLUMN),
									 errmsg("common column name \"%s\" appears more than once in left table",
											fc_u_colname)));
						fc_l_index = fc_ndx;
					}
					fc_ndx++;
				}
				if (fc_l_index < 0)
					ereport(ERROR,
							(errcode(ERRCODE_UNDEFINED_COLUMN),
							 errmsg("column \"%s\" specified in USING clause does not exist in left table",
									fc_u_colname)));
				fc_l_colnos = lappend_int(fc_l_colnos, fc_l_index + 1);

				/* 在右输入中查找 */
				fc_ndx = 0;
				foreach(fc_col, fc_r_colnames)
				{
					char	   *fc_r_colname = strVal(lfirst(fc_col));

					if (strcmp(fc_r_colname, fc_u_colname) == 0)
					{
						if (fc_r_index >= 0)
							ereport(ERROR,
									(errcode(ERRCODE_AMBIGUOUS_COLUMN),
									 errmsg("common column name \"%s\" appears more than once in right table",
											fc_u_colname)));
						fc_r_index = fc_ndx;
					}
					fc_ndx++;
				}
				if (fc_r_index < 0)
					ereport(ERROR,
							(errcode(ERRCODE_UNDEFINED_COLUMN),
							 errmsg("column \"%s\" specified in USING clause does not exist in right table",
									fc_u_colname)));
				fc_r_colnos = lappend_int(fc_r_colnos, fc_r_index + 1);

				fc_l_colvar = fc_buildVarFromNSColumn(fc_l_nscolumns + fc_l_index);
				fc_l_usingvars = lappend(fc_l_usingvars, fc_l_colvar);
				fc_r_colvar = fc_buildVarFromNSColumn(fc_r_nscolumns + fc_r_index);
				fc_r_usingvars = lappend(fc_r_usingvars, fc_r_colvar);

				fc_res_colnames = lappend(fc_res_colnames, lfirst(fc_ucol));
				fc_u_colvar = fc_buildMergedJoinVar(fc_pstate,
											  fc_j->jointype,
											  fc_l_colvar,
											  fc_r_colvar);
				fc_res_colvars = lappend(fc_res_colvars, fc_u_colvar);
				fc_res_nscolumn = fc_res_nscolumns + fc_res_colindex;
				fc_res_colindex++;
				if (fc_u_colvar == (Node *) fc_l_colvar)
				{
					/* 合并列等同于左输入 */
					*fc_res_nscolumn = fc_l_nscolumns[fc_l_index];
				}
				else if (fc_u_colvar == (Node *) fc_r_colvar)
				{
					/* 合并列等同于右输入 */
					*fc_res_nscolumn = fc_r_nscolumns[fc_r_index];
				}
				else
				{
					/*
					 * 合并列在语义上不等同于任何一个
					 * 输入，因此需要将其引用为连接输出
					 * 列。我们还不知道连接的 varno，因此我们将
					 * 在下面替换这些零。
					 */
					fc_res_nscolumn->p_varno = 0;
					fc_res_nscolumn->p_varattno = fc_res_colindex;
					fc_res_nscolumn->p_vartype = exprType(fc_u_colvar);
					fc_res_nscolumn->p_vartypmod = exprTypmod(fc_u_colvar);
					fc_res_nscolumn->p_varcollid = exprCollation(fc_u_colvar);
					fc_res_nscolumn->p_varnosyn = 0;
					fc_res_nscolumn->p_varattnosyn = fc_res_colindex;
				}
			}

			fc_j->quals = fc_transformJoinUsingClause(fc_pstate,
												fc_l_usingvars,
												fc_r_usingvars);
		}
		else if (fc_j->quals)
		{
			/* 用户编写的 ON 条件；转换它 */
			fc_j->quals = fc_transformJoinOnClause(fc_pstate, fc_j, fc_my_namespace);
		}
		else
		{
			/* CROSS JOIN：没有资格 */
		}

		/* 将每一侧剩余的列添加到输出列中 */
		fc_res_colindex +=
			fc_extractRemainingColumns(fc_l_nscolumns, fc_l_colnames, &fc_l_colnos,
									&fc_res_colnames, &fc_res_colvars,
									fc_res_nscolumns + fc_res_colindex);
		fc_res_colindex +=
			fc_extractRemainingColumns(fc_r_nscolumns, fc_r_colnames, &fc_r_colnos,
									&fc_res_colnames, &fc_res_colvars,
									fc_res_nscolumns + fc_res_colindex);

		/*
		 * 现在为连接的结果构建 RTE 和 nsitem。
		 * res_nscolumns 还没有完全完成，但没关系，因为
		 * addRangeTableEntryForJoin 不会检查它，只存储一个指针。
		 */
		fc_nsitem = addRangeTableEntryForJoin(fc_pstate,
										   fc_res_colnames,
										   fc_res_nscolumns,
										   fc_j->jointype,
										   list_length(fc_j->usingClause),
										   fc_res_colvars,
										   fc_l_colnos,
										   fc_r_colnos,
										   fc_j->join_using_alias,
										   fc_j->alias,
										   true);

		fc_j->rtindex = fc_nsitem->p_rtindex;

		/*
		 * 现在我们知道连接 RTE 的 rangetable 索引，我们可以修正
		 * res_nscolumns 数据，在应该包含它的地方。
		 */
		Assert(fc_res_colindex == list_length(fc_nsitem->p_names->colnames));
		for (fc_k = 0; fc_k < fc_res_colindex; fc_k++)
		{
			ParseNamespaceColumn *fc_nscol = fc_res_nscolumns + fc_k;

			/* 填充合并列的连接 RTI */
			if (fc_nscol->p_varno == 0)
				fc_nscol->p_varno = fc_j->rtindex;
			if (fc_nscol->p_varnosyn == 0)
				fc_nscol->p_varnosyn = fc_j->rtindex;
			/* 如果连接有别名，它在语法上隐藏所有输入 */
			if (fc_j->alias)
			{
				fc_nscol->p_varnosyn = fc_j->rtindex;
				fc_nscol->p_varattnosyn = fc_k + 1;
			}
		}

		/* 创建一个匹配链接到 JoinExpr 以供后用 */
		for (fc_k = list_length(fc_pstate->p_joinexprs) + 1; fc_k < fc_j->rtindex; fc_k++)
			fc_pstate->p_joinexprs = lappend(fc_pstate->p_joinexprs, NULL);
		fc_pstate->p_joinexprs = lappend(fc_pstate->p_joinexprs, fc_j);
		Assert(list_length(fc_pstate->p_joinexprs) == fc_j->rtindex);

		/*
		 * 如果连接有 USING 别名，请为其创建一个 ParseNamespaceItem，
		 * 并将其添加到连接输入的 nsitems 列表中。
		 */
		if (fc_j->join_using_alias)
		{
			ParseNamespaceItem *fc_jnsitem;

			fc_jnsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
			fc_jnsitem->p_names = fc_j->join_using_alias;
			fc_jnsitem->p_rte = fc_nsitem->p_rte;
			fc_jnsitem->p_rtindex = fc_nsitem->p_rtindex;
			/* 不需要复制前 N 列，只需使用 res_nscolumns */
			fc_jnsitem->p_nscolumns = fc_res_nscolumns;
			/* 设置默认可见性标志；可能稍后会更改 */
			fc_jnsitem->p_rel_visible = true;
			fc_jnsitem->p_cols_visible = true;
			fc_jnsitem->p_lateral_only = false;
			fc_jnsitem->p_lateral_ok = true;
			/* 根据 SQL，我们必须检查别名冲突 */
			checkNameSpaceConflicts(fc_pstate, list_make1(fc_jnsitem), fc_my_namespace);
			fc_my_namespace = lappend(fc_my_namespace, fc_jnsitem);
		}

		/*
		 * 准备返回的命名空间列表。如果 JOIN 有别名，则其
		 * 完全隐藏所包含的 RTE；否则，所包含的 RTE
		 * 仍然可见作为表名，但对
		 * 非限定列名访问不可见。
		 *
		 * 注意：如果存在嵌套的无别名 JOIN，较低级的 JOIN
		 * 将保持在列表中，即使它们没有设置
		 * p_rel_visible 或 p_cols_visible。我们可以删除这样的列表项，
		 * 但不清楚是否值得花费周期来做到这一点。
		 */
		if (fc_j->alias != NULL)
			fc_my_namespace = NIL;
		else
			fc_setNamespaceColumnVisibility(fc_my_namespace, false);

		/*
		 * 连接 RTE 本身始终对非限定列名可见。
		 * 仅当它具有别名时才作为关系名可见。
		 */
		fc_nsitem->p_rel_visible = (fc_j->alias != NULL);
		fc_nsitem->p_cols_visible = true;
		fc_nsitem->p_lateral_only = false;
		fc_nsitem->p_lateral_ok = true;

		*fc_top_nsitem = fc_nsitem;
		*fc_namespace = lappend(fc_my_namespace, fc_nsitem);

		return (Node *) fc_j;
	}
	else
		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_n));
	return NULL;				/* 不能在这里出现，保持编译器安静 */
}

/*
 * buildVarFromNSColumn -
 *	  使用 ParseNamespaceColumn 数据构建一个 Var 节点
 *
 * 我们假设 varlevelsup 应该为 0，并且没有指定位置
 */
static Var * fc_buildVarFromNSColumn(ParseNamespaceColumn *fc_nscol)
{
	Var		   *fc_var;

	Assert(fc_nscol->p_varno > 0); /* 也就是说，未删除的列 */
	fc_var = makeVar(fc_nscol->p_varno,
				  fc_nscol->p_varattno,
				  fc_nscol->p_vartype,
				  fc_nscol->p_vartypmod,
				  fc_nscol->p_varcollid,
				  0);
	/* makeVar 不提供这些参数，因此手动设置： */
	fc_var->varnosyn = fc_nscol->p_varnosyn;
	fc_var->varattnosyn = fc_nscol->p_varattnosyn;
	return fc_var;
}

/*
 * buildMergedJoinVar -
 *	  生成合适的替代表达式以用于合并连接列
 */
static Node * fc_buildMergedJoinVar(ParseState *fc_pstate, JoinType fc_jointype,
				   Var *fc_l_colvar, Var *fc_r_colvar)
{
	Oid			fc_outcoltype;
	int32		fc_outcoltypmod;
	Node	   *fc_l_node,
			   *fc_r_node,
			   *fc_res_node;

	fc_outcoltype = select_common_type(fc_pstate,
									list_make2(fc_l_colvar, fc_r_colvar),
									"JOIN/USING",
									NULL);
	fc_outcoltypmod = select_common_typmod(fc_pstate,
										list_make2(fc_l_colvar, fc_r_colvar),
										fc_outcoltype);

	/*
	 * 如果需要，插入类型转换函数。注意，typmod 的差异
	 * 只会发生在输入具有 typmod 而 outcoltypmod 为 -1 的情况下。在那种
	 * 情况下，我们插入一个 RelabelType 来清楚地标记结果的 typmod
	 * 与输入不同。我们从来不需要 coerce_type_typmod。
	 */
	if (fc_l_colvar->vartype != fc_outcoltype)
		fc_l_node = coerce_type(fc_pstate, (Node *) fc_l_colvar, fc_l_colvar->vartype,
							 fc_outcoltype, fc_outcoltypmod,
							 COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1);
	else if (fc_l_colvar->vartypmod != fc_outcoltypmod)
		fc_l_node = (Node *) makeRelabelType((Expr *) fc_l_colvar,
										  fc_outcoltype, fc_outcoltypmod,
										  InvalidOid,	/* 在下面修复 */
										  COERCE_IMPLICIT_CAST);
	else
		fc_l_node = (Node *) fc_l_colvar;

	if (fc_r_colvar->vartype != fc_outcoltype)
		fc_r_node = coerce_type(fc_pstate, (Node *) fc_r_colvar, fc_r_colvar->vartype,
							 fc_outcoltype, fc_outcoltypmod,
							 COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1);
	else if (fc_r_colvar->vartypmod != fc_outcoltypmod)
		fc_r_node = (Node *) makeRelabelType((Expr *) fc_r_colvar,
										  fc_outcoltype, fc_outcoltypmod,
										  InvalidOid,	/* 在下面修复 */
										  COERCE_IMPLICIT_CAST);
	else
		fc_r_node = (Node *) fc_r_colvar;

	/*
	 * 选择要发出的内容
	 */
	switch (fc_jointype)
	{
		case JOIN_INNER:

			/*
			 * 我们可以使用任一 var；如果可用，优先使用未类型转换的。
			 */
			if (IsA(fc_l_node, Var))
				fc_res_node = fc_l_node;
			else if (IsA(fc_r_node, Var))
				fc_res_node = fc_r_node;
			else
				fc_res_node = fc_l_node;
			break;
		case JOIN_LEFT:
			/* 始终使用左 var */
			fc_res_node = fc_l_node;
			break;
		case JOIN_RIGHT:
			/* 始终使用右 var */
			fc_res_node = fc_r_node;
			break;
		case JOIN_FULL:
			{
				/*
				 * 在这里，我们必须构建一个 COALESCE 表达式，以确保
				 * 如果任一输入为非空，则连接输出也是非空。
				 */
				CoalesceExpr *fc_c = makeNode(CoalesceExpr);

				fc_c->coalescetype = fc_outcoltype;
				/* coalescecollid 将在下面设置 */
				fc_c->args = list_make2(fc_l_node, fc_r_node);
				fc_c->location = -1;
				fc_res_node = (Node *) fc_c;
				break;
			}
		default:
			elog(ERROR, "unrecognized join type: %d", (int) fc_jointype);
			fc_res_node = NULL;	/* 保持编译器安静 */
			break;
	}

	/*
	 * 应用 assign_expr_collations 以修正
	 * 类型转换和 CoalesceExpr 节点中的排序信息（如果我们进行了任何修改）。这必须现在完成，
	 * 以便连接节点的别名变量显示正确的排序信息。
	 */
	assign_expr_collations(fc_pstate, fc_res_node);

	return fc_res_node;
}

/*
 * setNamespaceColumnVisibility -
 *	  更新命名空间列表中的 cols_visible 标志的便利子例程。
 */
static void fc_setNamespaceColumnVisibility(List *fc_namespace, bool fc_cols_visible)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_namespace)
	{
		ParseNamespaceItem *fc_nsitem = (ParseNamespaceItem *) lfirst(fc_lc);

		fc_nsitem->p_cols_visible = fc_cols_visible;
	}
}

/*
 * setNamespaceLateralState -
 *	  更新命名空间列表中的 LATERAL 标志的便利子例程。
 */
static void fc_setNamespaceLateralState(List *fc_namespace, bool fc_lateral_only, bool fc_lateral_ok)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_namespace)
	{
		ParseNamespaceItem *fc_nsitem = (ParseNamespaceItem *) lfirst(fc_lc);

		fc_nsitem->p_lateral_only = fc_lateral_only;
		fc_nsitem->p_lateral_ok = fc_lateral_ok;
	}
}


/*
 * transformWhereClause -
 *	  转换资格并确保其为布尔类型。
 *	  用于 WHERE 和相关子句。
 *
 * constructName 不影响语义，但在错误消息中使用
 */
Node * transformWhereClause(ParseState *fc_pstate, Node *fc_clause,
					 ParseExprKind fc_exprKind, const char *fc_constructName)
{
	Node	   *fc_qual;

	if (fc_clause == NULL)
		return NULL;

	fc_qual = transformExpr(fc_pstate, fc_clause, fc_exprKind);

	fc_qual = coerce_to_boolean(fc_pstate, fc_qual, fc_constructName);

	return fc_qual;
}


/*
 * transformLimitClause -
 *	  转换表达式并确保其为 bigint 类型。
 *	  用于 LIMIT 和相关子句。
 *
 * 注意：从 Postgres 8.2 开始，LIMIT 表达式预期返回 int8，
 * 而不是之前的 int4。
 *
 * constructName 不影响语义，但在错误消息中使用
 */
Node * transformLimitClause(ParseState *fc_pstate, Node *fc_clause,
					 ParseExprKind fc_exprKind, const char *fc_constructName,
					 LimitOption fc_limitOption)
{
	Node	   *fc_qual;

	if (fc_clause == NULL)
		return NULL;

	fc_qual = transformExpr(fc_pstate, fc_clause, fc_exprKind);

	fc_qual = coerce_to_specific_type(fc_pstate, fc_qual, INT8OID, fc_constructName);

	/* LIMIT 不能引用当前查询的任何变量 */
	fc_checkExprIsVarFree(fc_pstate, fc_qual, fc_constructName);

	/*
	 * 不允许在 FETCH FIRST .. WITH TIES 中使用 NULL。这个测试是丑陋和
	 * 极其简单的，因为你可以通过将其隐藏在表达式中而仍然传递 NULL -- 但是这保护了 ruleutils，防止发出一个
	 * 未修饰的 NULL，而该 NULL 没有被语法接受。
	 */
	if (fc_exprKind == EXPR_KIND_LIMIT && fc_limitOption == LIMIT_OPTION_WITH_TIES &&
		IsA(fc_clause, A_Const) && castNode(A_Const, fc_clause)->isnull)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE),
				 errmsg("row count cannot be null in FETCH FIRST ... WITH TIES clause")));

	return fc_qual;
}

/*
 * checkExprIsVarFree
 *		检查给定的表达式在当前查询级别没有 Vars
 *		（聚合和窗口函数应已被拒绝）。
 *
 * 这用于检查必须在查询的所有行中具有一致值的表达式，
 * 例如 LIMIT。可以说，它也应该拒绝易变函数，但我们并没有这样做 --- 函数在首次执行时返回的任何值就是你得到的。
 *
 * constructName 不影响语义，但在错误消息中使用
 */
static void fc_checkExprIsVarFree(ParseState *fc_pstate, Node *fc_n, const char *fc_constructName)
{
	if (contain_vars_of_level(fc_n, 0))
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
		/* 翻译者：%s 是 SQL 构造的名称，例如 LIMIT */
				 errmsg("argument of %s must not contain variables",
						fc_constructName),
				 parser_errposition(fc_pstate,
									locate_var_of_level(fc_n, 0))));
	}
}


/*
 * checkTargetlistEntrySQL92 -
 *	  验证由 findTargetlistEntrySQL92 找到的目标列表条目
 *
 * 当我们选择现有的 tlist 条目作为语法结果
 * 例如 "GROUP BY 1" 时，我们必须确保它适合在
 * 指定的子句类型中使用； transformExpr() 将其视为常规的目标
 * 列表项。
 */
static void fc_checkTargetlistEntrySQL92(ParseState *fc_pstate, TargetEntry *fc_tle,
						  ParseExprKind fc_exprKind)
{
	switch (fc_exprKind)
	{
		case EXPR_KIND_GROUP_BY:
			/* 拒绝聚合和窗口函数 */
			if (fc_pstate->p_hasAggs &&
				contain_aggs_of_level((Node *) fc_tle->expr, 0))
				ereport(ERROR,
						(errcode(ERRCODE_GROUPING_ERROR),
				/* 翻译者：%s 是 SQL 构造的名称，例如 GROUP BY */
						 errmsg("aggregate functions are not allowed in %s",
								ParseExprKindName(fc_exprKind)),
						 parser_errposition(fc_pstate,
											locate_agg_of_level((Node *) fc_tle->expr, 0))));
			if (fc_pstate->p_hasWindowFuncs &&
				contain_windowfuncs((Node *) fc_tle->expr))
				ereport(ERROR,
						(errcode(ERRCODE_WINDOWING_ERROR),
				/* 翻译者：%s 是 SQL 构造的名称，例如 GROUP BY */
						 errmsg("window functions are not allowed in %s",
								ParseExprKindName(fc_exprKind)),
						 parser_errposition(fc_pstate,
											locate_windowfunc((Node *) fc_tle->expr))));
			break;
		case EXPR_KIND_ORDER_BY:
			/* 无需额外检查 */
			break;
		case EXPR_KIND_DISTINCT_ON:
			/* 无需额外检查 */
			break;
		default:
			elog(ERROR, "unexpected exprKind in checkTargetlistEntrySQL92");
			break;
	}
}

/*
 *	findTargetlistEntrySQL92 -
 *	  返回与给定（未转换）节点匹配的目标列表条目。
 *	  如果没有匹配的条目，则创建一个并将其附加到目标
 *	  列表，作为 "resjunk" 节点。
 *
 * 此函数支持旧的 SQL92 ORDER BY 解释，其中
 * 表达式是输出列名或数字。如果我们未找到这种匹配，
 * 则会转到 SQL99 规则。出于历史原因，Postgres 也允许对 GROUP BY 
 * 使用这种解释，尽管标准从未这样做。然而，对于 GROUP BY 
 *，我们更倾向于 SQL99 匹配。
 * 此函数 *不* 用于窗口定义。
 *
 * node		要匹配的 ORDER BY、GROUP BY 或 DISTINCT ON 表达式
 * tlist	目标列表（通过引用传递，以便我们可以附加到它）
 * exprKind 识别正在处理的子句类型
 */
static TargetEntry * fc_findTargetlistEntrySQL92(ParseState *fc_pstate, Node *fc_node, List **fc_tlist,
						 ParseExprKind fc_exprKind)
{
	ListCell   *fc_tl;

	
/*---------
	 * 处理SQL92规范规定的两个特殊情况：
	 *
	 * 1. 裸列名（没有限定符或下标）
	 *	  对于裸标识符，我们检查现有目标列表中是否有匹配的列名。
	 *	  多个匹配是一个错误，除非它们指的是相同的值；例如，
	 *	  我们允许	SELECT a, a FROM table ORDER BY a
	 *	  但不允许	SELECT a AS b, b FROM table ORDER BY b
	 *	  如果没有找到匹配项，我们就会继续处理，将标识符
	 *	  视为表达式。
	 *	  对于GROUP BY，将分组项与目标列表条目进行匹配是不正确的：
	 *	  根据SQL92，GROUP BY中的标识符是对FROM中暴露的列名的引用，而不是对目标
	 *	  列的引用。然而，许多实现（包括7.0之前的PostgreSQL）依然接受这一点。
	 *	  因此，对于GROUP BY，我们首先检查标识符是否与任何FROM列名匹配，只有
	 *	  在没有匹配时才尝试目标列表名称。这确保了在名称都可能存在的情况下
	 *	  我们遵循规范。DISTINCT ON不在标准中，因此我们可以根据自己的喜好进行处理；
	 *	  我们选择让它像ORDER BY一样工作，理由是普通的DISTINCT在目标列表条目上工作。
	 *
	 * 2. 整数常量
	 *	  这意味着使用现有目标列表中的第n个项目。
	 *	  请注意，按实际常量排序/分组/去重是没有意义的，因此这与SQL99没有冲突。
	 *	  SQL92不允许按列编号进行GROUP BY，但由于标准对这种语法没有定义其他行为，
	 *	  我们也可以接受这个常见扩展。
	 *
	 * 请注意，在这两种情况下，必须不使用现有的resjunk目标，
	 * 因为用户并没有在他的SELECT列表中写入它们。
	 *
	 * 如果没有适用的特殊情况，则继续将该项视为
	 * 根据SQL99的表达式。
	 *----------
	 */
	if (IsA(fc_node, ColumnRef) &&
		list_length(((ColumnRef *) fc_node)->fields) == 1 &&
		IsA(linitial(((ColumnRef *) fc_node)->fields), String))
	{
		char	   *fc_name = strVal(linitial(((ColumnRef *) fc_node)->fields));
		int			fc_location = ((ColumnRef *) fc_node)->location;

		if (fc_exprKind == EXPR_KIND_GROUP_BY)
		{
			/*
			 * 在GROUP BY中，我们必须优先匹配来自FROM子句
			 * 的列，而不是目标列表。查看是否有
			 * 匹配的列。如果有，则继续使用SQL99规则。
			 * 注意：如果名称可能模糊地指代FROM中暴露的多个列名，
			 * colNameToVar将引发错误。这正是我们在这里想要的。
			 *
			 * 对于7.4.3的小调整：忽略上层查询中的匹配。
			 * 这有效地将裸名称的搜索顺序更改为（1）
			 * 本地FROM变量，（2）本地目标列表别名，（3）外部
			 * FROM变量，而之前是（1）（3）（2）。SQL92和
			 * SQL99不允许按外部引用进行分组，因此这不会破坏任何
			 * 合法的规范情况，看起来更符合自洽的行为。
			 */
			if (colNameToVar(fc_pstate, fc_name, true, fc_location) != NULL)
				fc_name = NULL;
		}

		if (fc_name != NULL)
		{
			TargetEntry *fc_target_result = NULL;

			foreach(fc_tl, *fc_tlist)
			{
				TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_tl);

				if (!fc_tle->resjunk &&
					strcmp(fc_tle->resname, fc_name) == 0)
				{
					if (fc_target_result != NULL)
					{
						if (!equal(fc_target_result->expr, fc_tle->expr))
							ereport(ERROR,
									(errcode(ERRCODE_AMBIGUOUS_COLUMN),

							/*------
							  翻译者：第一个 %s 是SQL构造的名称，例如 ORDER BY */
									 errmsg("%s \"%s\" is ambiguous",
											ParseExprKindName(fc_exprKind),
											fc_name),
									 parser_errposition(fc_pstate, fc_location)));
					}
					else
						fc_target_result = fc_tle;
					/* 保持在循环中以检查模糊性 */
				}
			}
			if (fc_target_result != NULL)
			{
				/* 返回第一个匹配项，经过适当验证后 */
				fc_checkTargetlistEntrySQL92(fc_pstate, fc_target_result, fc_exprKind);
				return fc_target_result;
			}
		}
	}
	if (IsA(fc_node, A_Const))
	{
		A_Const    *fc_aconst = castNode(A_Const, fc_node);
		int			fc_targetlist_pos = 0;
		int			fc_target_pos;

		if (!IsA(&fc_aconst->val, Integer))
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
			/* 翻译者：%s 是 SQL 构造的名称，例如 ORDER BY */
					 errmsg("non-integer constant in %s",
							ParseExprKindName(fc_exprKind)),
					 parser_errposition(fc_pstate, fc_aconst->location)));

		fc_target_pos = intVal(&fc_aconst->val);
		foreach(fc_tl, *fc_tlist)
		{
			TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_tl);

			if (!fc_tle->resjunk)
			{
				if (++fc_targetlist_pos == fc_target_pos)
				{
					/* 返回唯一匹配项，经过适当验证后 */
					fc_checkTargetlistEntrySQL92(fc_pstate, fc_tle, fc_exprKind);
					return fc_tle;
				}
			}
		}
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
		/* 翻译者：%s 是 SQL 构造的名称，例如 ORDER BY */
				 errmsg("%s position %d is not in select list",
						ParseExprKindName(fc_exprKind), fc_target_pos),
				 parser_errposition(fc_pstate, fc_aconst->location)));
	}

	/*
	 *否则，我们有一个表达式，因此根据 SQL99 规则处理它。
	 */
	return fc_findTargetlistEntrySQL99(fc_pstate, fc_node, fc_tlist, fc_exprKind);
}

/*
 *	findTargetlistEntrySQL99 -
 *	  返回与给定（未转换）节点匹配的目标列表项。
 *	  如果不存在匹配项，则创建一个并将其附加到目标
 *	  列表中作为“resjunk”节点。
 *
 * 此函数支持 SQL99 的解释，其中表达式仅是引用输入列名的普通表达式。
 *
 * node	要匹配的 ORDER BY、GROUP BY 等表达式
 * tlist	目标列表（按引用传递，以便我们可以附加到它）
 * exprKind 确定正在处理的子句类型
 */
static TargetEntry * fc_findTargetlistEntrySQL99(ParseState *fc_pstate, Node *fc_node, List **fc_tlist,
						 ParseExprKind fc_exprKind)
{
	TargetEntry *fc_target_result;
	ListCell   *fc_tl;
	Node	   *fc_expr;

	/*
	 * 将未转换的节点转换为转换后的表达式，并搜索
	 * 在目标列表中寻找匹配项。 注意：是否存在多个匹配项
	 * 其实并不重要。此外，我们愿意在这里匹配一个现有的
	 * resjunk 目标，尽管上面的 SQL92 用例必须忽略 resjunk
	 * 目标。
	 */
	fc_expr = transformExpr(fc_pstate, fc_node, fc_exprKind);

	foreach(fc_tl, *fc_tlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_tl);
		Node	   *fc_texpr;

		/*
		 * 忽略现有目标列表表达式上的任何隐式转换。
		 *
		 * 这实质上允许 ORDER/GROUP 等项采用先前为文本等价目标列表项
		 * 选择的相同数据类型。在这个阶段，普通 SELECT 目标列表的顶层
		 * 不会有隐式转换，但在聚合函数中的 ORDER BY 的情况下会出现。
		 */
		fc_texpr = strip_implicit_coercions((Node *) fc_tle->expr);

		if (equal(fc_expr, fc_texpr))
			return fc_tle;
	}

	/*
	 * 如果没有匹配项，构造一个新的目标条目，并附加到
	 * 目标列表的末尾。该目标被赋予 resjunk = true，以便
	 * 它不会被投影到最终元组中。
	 */
	fc_target_result = transformTargetEntry(fc_pstate, fc_node, fc_expr, fc_exprKind,
										 NULL, true);

	*fc_tlist = lappend(*fc_tlist, fc_target_result);

	return fc_target_result;
}

/*-------------------------------------------------------------------------
 * 扁平化分组列表中的带括号的子列表，以及一些嵌套分组集的情况。
 *
 * 在一个分组集（ROLLUP、CUBE 或 GROUPING SETS）中，我们期望
 * 内容的嵌套深度不会超过 2：即 ROLLUP((a,b),(c,d)) 是
 * 可以的，但 ROLLUP((a,(b,c)),d) 会被扁平化为 ((a,b,c),d)，
 * 然后我们将其（稍后）规范化为 ((a,b,c),(d))。
 *
 * CUBE 或 ROLLUP 可以嵌套在 GROUPING SETS 内（但不能反向），
 * 如果我们发现这一点，就让它保持不变。但是，如果我们在
 * GROUPING SETS 中发现 GROUPING SETS，我们可以像下面这样扁平化
 * 并规范化：
 *	 GROUPING SETS (a, (b,c), GROUPING SETS ((c,d),(e)), (f,g))
 * 变为
 *	 GROUPING SETS ((a), (b,c), (c,d), (e), (f,g))
 *
 * 这符合规范的语法转换，但这些是我们在解析分析中进行的唯一
 * 此类转换，以便在去解析时，查询尽可能保留原始指定的
 * CUBE 和 ROLLUP 的分组选集语法。（将结果完全扩展为分组集列表留给
 * 规划器。）
 *
 * 当我们完成时，生成的列表应仅包含这些可能的
 * 元素：
 *	 - 一个表达式
 *	 - 一个包含嵌套 2 层表达式列表的 CUBE 或 ROLLUP
 *	 - 一个 GROUPING SET，包含任何：
 *		- 表达式列表
 *		- 空分组集
 *		- 具有嵌套 2 层列表的 CUBE 或 ROLLUP 节点
 * 返回的是一个新列表，但不会深拷贝旧节点，除了
 * GroupingSet 节点。
 *
 * 作为副作用，标记列表是否有任何 GroupingSet 节点。
 *-------------------------------------------------------------------------
 */
static Node * fc_flatten_grouping_sets(Node *fc_expr, bool fc_toplevel, bool *fc_hasGroupingSets)
{
	/* 以防止病态的输入 */
	check_stack_depth();

	if (fc_expr == (Node *) NIL)
		return (Node *) NIL;

	switch (fc_expr->type)
	{
		case T_RowExpr:
			{
				RowExpr    *fc_r = (RowExpr *) fc_expr;

				if (fc_r->row_format == COERCE_IMPLICIT_CAST)
					return fc_flatten_grouping_sets((Node *) fc_r->args,
												 false, NULL);
			}
			break;
		case T_GroupingSet:
			{
				GroupingSet *fc_gset = (GroupingSet *) fc_expr;
				ListCell   *fc_l2;
				List	   *fc_result_set = NIL;

				if (fc_hasGroupingSets)
					*fc_hasGroupingSets = true;

				/*
				 * 在顶层，我们跳过所有空分组集；调用者可以
				 * 提供规范的 GROUP BY () 如果没有剩余。
				 */

				if (fc_toplevel && fc_gset->kind == GROUPING_SET_EMPTY)
					return (Node *) NIL;

				foreach(fc_l2, fc_gset->content)
				{
					Node	   *fc_n1 = lfirst(fc_l2);
					Node	   *fc_n2 = fc_flatten_grouping_sets(fc_n1, false, NULL);

					if (IsA(fc_n1, GroupingSet) &&
						((GroupingSet *) fc_n1)->kind == GROUPING_SET_SETS)
						fc_result_set = list_concat(fc_result_set, (List *) fc_n2);
					else
						fc_result_set = lappend(fc_result_set, fc_n2);
				}

				/*
				 * 在顶层，保留分组集节点；但如果我们在
				 * 嵌套的分组集中，则需要将扁平化的
				 * 结果连接到外部列表中（如果它仅仅是嵌套）。
				 */

				if (fc_toplevel || (fc_gset->kind != GROUPING_SET_SETS))
				{
					return (Node *) makeGroupingSet(fc_gset->kind, fc_result_set, fc_gset->location);
				}
				else
					return (Node *) fc_result_set;
			}
		case T_List:
			{
				List	   *fc_result = NIL;
				ListCell   *fc_l;

				foreach(fc_l, (List *) fc_expr)
				{
					Node	   *fc_n = fc_flatten_grouping_sets(lfirst(fc_l), fc_toplevel, fc_hasGroupingSets);

					if (fc_n != (Node *) NIL)
					{
						if (IsA(fc_n, List))
							fc_result = list_concat(fc_result, (List *) fc_n);
						else
							fc_result = lappend(fc_result, fc_n);
					}
				}

				return (Node *) fc_result;
			}
		default:
			break;
	}

	return fc_expr;
}


/*
 * 转换 GROUP BY 子句或分组集中的单个表达式。
 *
 * 如果目标列表中尚未存在该表达式，则将其添加到目标列表中，并且如果
 * 在 flatresult 列表中尚未存在该表达式，则将其添加到 flatresult 列表中
 * （flatresult 将变为 groupClause）。sortClause 中的操作符和排序顺序提示会被参考。
 *
 * 返回表达式的 ressortgroupref。
 *
 * flatresult	对 SortGroupClause 节点的扁平列表的引用
 * seen_local	本地级别已看到的 sortgrouprefs 的位图集
 * pstate		解析状态
 * gexpr		要转换的节点
 * targetlist	对 TargetEntry 列表的引用
 * sortClause	ORDER BY 子句（SortGroupClause 节点）
 * exprKind		表达式类型
 * useSQL99		使用 SQL99 而非 SQL92 语法
 * toplevel		如果处于任何分组集内则为 false
 */
static Index
fc_transformGroupClauseExpr(List **fc_flatresult, Bitmapset *fc_seen_local,
						 ParseState *fc_pstate, Node *fc_gexpr,
						 List **fc_targetlist, List *fc_sortClause,
						 ParseExprKind fc_exprKind, bool fc_useSQL99, bool fc_toplevel)
{
	TargetEntry *fc_tle;
	bool		fc_found = false;

	if (fc_useSQL99)
		fc_tle = fc_findTargetlistEntrySQL99(fc_pstate, fc_gexpr,
									   fc_targetlist, fc_exprKind);
	else
		fc_tle = fc_findTargetlistEntrySQL92(fc_pstate, fc_gexpr,
									   fc_targetlist, fc_exprKind);

	if (fc_tle->ressortgroupref > 0)
	{
		ListCell   *fc_sl;

		/* 
		 * 在本地级别消除重复项 (GROUP BY x, x)。
		 * （分组集中的重复项可能会影响返回的行数，因此不能不加区别地丢弃它们。）
		 *
		 * 由于我们只关心 sortgroupref，因此可以使用位图集而非扫描列表。
		 */
		if (bms_is_member(fc_tle->ressortgroupref, fc_seen_local))
			return 0;

		/*
		 * 如果我们已经在扁平子句列表中，则不需要考虑
		 * 再次添加自己。
		 */
		fc_found = targetIsInSortList(fc_tle, InvalidOid, *fc_flatresult);
		if (fc_found)
			return fc_tle->ressortgroupref;

		/*
		 * 如果 GROUP BY tlist 条目也出现在 ORDER BY 中，则从
		 * （第一个）匹配的 ORDER BY 项中复制操作符信息。
		 * 这意味着，如果您写下 "GROUP BY foo ORDER BY foo USING <<<"，那么
		 * GROUP BY 操作将默默接受 ORDER BY 隐含的相等语义。
		 * 有两个理由这样做：它提高了我们可以用单一步排序步骤实现 GROUP BY 和 ORDER BY 的几率，
		 * 并且允许用户选择 GROUP BY 使用的相等语义，如果她正在处理具有
		 * 多个相等操作符的数据类型。
		 *
		 * 然而，如果我们在一个分组集中，我们强制请求的排序
		 * 为 NULLS LAST，因为如果我们希望对该任务使用排序聚合，
		 * 我们将会在相应的组后添加生成的 NULL 值。
		 * 如果用户要求 nulls first，则另一步排序是必然的，但那是
		 * 规划器的问题。
		 */

		foreach(fc_sl, fc_sortClause)
		{
			SortGroupClause *fc_sc = (SortGroupClause *) lfirst(fc_sl);

			if (fc_sc->tleSortGroupRef == fc_tle->ressortgroupref)
			{
				SortGroupClause *fc_grpc = copyObject(fc_sc);

				if (!fc_toplevel)
					fc_grpc->nulls_first = false;
				*fc_flatresult = lappend(*fc_flatresult, fc_grpc);
				fc_found = true;
				break;
			}
		}
	}

	/*
	 * 如果在 ORDER BY 中没有匹配项，就使用默认
	 * 排序/分组语义将其添加到结果中。
	 */
	if (!fc_found)
		*fc_flatresult = fc_addTargetToGroupList(fc_pstate, fc_tle,
										   *fc_flatresult, *fc_targetlist,
										   exprLocation(fc_gexpr));

	/*
	 * _某些东西_ 现在必须已为我们分配了一个 sortgroupref...
	 */

	return fc_tle->ressortgroupref;
}

/*
 * 转换 GROUP BY 子句或分组集中的表达式列表。
 *
 * 该表达式列表属于一个单独的子句，可以安全地消除重复项。
 *
 * 返回一个整数列表，包含 ressortgroupref 值。
 *
 * flatresult	对 SortGroupClause 节点的扁平列表的引用
 * pstate		解析状态
 * list			要转换的节点
 * targetlist	对 TargetEntry 列表的引用
 * sortClause	ORDER BY 子句（SortGroupClause 节点）
 * exprKind		表达式类型
 * useSQL99		使用 SQL99 而非 SQL92 语法
 * toplevel		如果处于任何分组集内则为 false
 */
static List * fc_transformGroupClauseList(List **fc_flatresult,
						 ParseState *fc_pstate, List *fc_list,
						 List **fc_targetlist, List *fc_sortClause,
						 ParseExprKind fc_exprKind, bool fc_useSQL99, bool fc_toplevel)
{
	Bitmapset  *fc_seen_local = NULL;
	List	   *fc_result = NIL;
	ListCell   *fc_gl;

	foreach(fc_gl, fc_list)
	{
		Node	   *fc_gexpr = (Node *) lfirst(fc_gl);

		Index		fc_ref = fc_transformGroupClauseExpr(fc_flatresult,
												   fc_seen_local,
												   fc_pstate,
												   fc_gexpr,
												   fc_targetlist,
												   fc_sortClause,
												   fc_exprKind,
												   fc_useSQL99,
												   fc_toplevel);

		if (fc_ref > 0)
		{
			fc_seen_local = bms_add_member(fc_seen_local, fc_ref);
			fc_result = lappend_int(fc_result, fc_ref);
		}
	}

	return fc_result;
}

/*
 * 转换一个分组集及其内容（递归）。
 *
 * 分组集可能是一组包含其他分组集的 GROUPING SETS 节点，
 * 但 SETS 中的 SETS 已在到达此处之前被展平。
 *
 * 返回转换后的节点，该节点现在包含 SIMPLE 节点及
 * 其列表的 ressortgrouprefs 而不是表达式。
 *
 * flatresult	对 SortGroupClause 节点的扁平列表的引用
 * pstate		解析状态
 * gset			要转换的分组集
 * targetlist	对 TargetEntry 列表的引用
 * sortClause	ORDER BY 子句（SortGroupClause 节点）
 * exprKind		表达式类型
 * useSQL99		使用 SQL99 而非 SQL92 语法
 * toplevel		如果处于任何分组集内则为 false
 */
static Node * fc_transformGroupingSet(List **fc_flatresult,
					 ParseState *fc_pstate, GroupingSet *fc_gset,
					 List **fc_targetlist, List *fc_sortClause,
					 ParseExprKind fc_exprKind, bool fc_useSQL99, bool fc_toplevel)
{
	ListCell   *fc_gl;
	List	   *fc_content = NIL;

	Assert(fc_toplevel || fc_gset->kind != GROUPING_SET_SETS);

	foreach(fc_gl, fc_gset->content)
	{
		Node	   *fc_n = lfirst(fc_gl);

		if (IsA(fc_n, List))
		{
			List	   *fc_l = fc_transformGroupClauseList(fc_flatresult,
													 fc_pstate, (List *) fc_n,
													 fc_targetlist, fc_sortClause,
													 fc_exprKind, fc_useSQL99, false);

			fc_content = lappend(fc_content, makeGroupingSet(GROUPING_SET_SIMPLE,
													   fc_l,
													   exprLocation(fc_n)));
		}
		else if (IsA(fc_n, GroupingSet))
		{
			GroupingSet *fc_gset2 = (GroupingSet *) lfirst(fc_gl);

			fc_content = lappend(fc_content, fc_transformGroupingSet(fc_flatresult,
															fc_pstate, fc_gset2,
															fc_targetlist, fc_sortClause,
															fc_exprKind, fc_useSQL99, false));
		}
		else
		{
			Index		fc_ref = fc_transformGroupClauseExpr(fc_flatresult,
													   NULL,
													   fc_pstate,
													   fc_n,
													   fc_targetlist,
													   fc_sortClause,
													   fc_exprKind,
													   fc_useSQL99,
													   false);

			fc_content = lappend(fc_content, makeGroupingSet(GROUPING_SET_SIMPLE,
													   list_make1_int(fc_ref),
													   exprLocation(fc_n)));
		}
	}

	/* 任意限制 CUBE 的大小，因其具有指数增长 */
	if (fc_gset->kind == GROUPING_SET_CUBE)
	{
		if (list_length(fc_content) > 12)
			ereport(ERROR,
					(errcode(ERRCODE_TOO_MANY_COLUMNS),
					 errmsg("CUBE is limited to 12 elements"),
					 parser_errposition(fc_pstate, fc_gset->location)));
	}

	return (Node *) makeGroupingSet(fc_gset->kind, fc_content, fc_gset->location);
}



/*
 * transformGroupClause -
 *	  转换 GROUP BY 子句
 *
 * GROUP BY 项目将被添加到目标列表中（作为 resjunk 列）
 * 如果尚未存在，因此目标列表必须通过引用传递。
 *
 * 这也用于窗口 PARTITION BY 子句（其作用几乎相同，
 * 但始终根据 SQL99 规则进行解释）。
 *
 * 分组集使这变得比以前复杂得多。我们的目标在于
 * 双重：我们创建一个扁平的 SortGroupClause 节点列表，引用每个
 * 用于分组的不同表达式，如果需要，这些表达式将被添加到
 * 目标列表中。与此同时，我们构建分组集树，
 * 该树仅在 GroupingSet 节点内存储 ressortgrouprefs 作为整数列表
 * （可能是嵌套的，但深度有限：一个 GROUPING_SET_SETS 节点可以包含
 * 嵌套的 SIMPLE、CUBE 或 ROLLUP 节点，但不能包含更多的集——我们将其扁平化；
 * 而 CUBE 和 ROLLUP 只能包含 SIMPLE 节点）。
 *
 * 如果没有分组集，我们可以跳过许多繁重的工作。
 *
 * 一个细微之处是，groupClause 列表在分组集列表不为空的情况下
 * 可能会变为空；如果只有空的分组集，或者显式的 GROUP BY ()，
 * 就会发生这种情况。这与指定
 * 聚合或没有 GROUP BY 的 HAVING 子句具有相同的效果；
 * 每个分组集的输出是每个分组集一行，即使输入为空。
 *
 * 返回转换后的（扁平）groupClause。
 *
 * pstate		ParseState
 * grouplist	要转换的子句
 * groupingSets 参考以包含分组集树的列表
 * targetlist	对 TargetEntry 列表的引用
 * sortClause	ORDER BY 子句（SortGroupClause 节点）
 * exprKind		表达式类型
 * useSQL99		SQL99 而非 SQL92 语法
 */
List * transformGroupClause(ParseState *fc_pstate, List *fc_grouplist, List **fc_groupingSets,
					 List **fc_targetlist, List *fc_sortClause,
					 ParseExprKind fc_exprKind, bool fc_useSQL99)
{
	List	   *fc_result = NIL;
	List	   *fc_flat_grouplist;
	List	   *fc_gsets = NIL;
	ListCell   *fc_gl;
	bool		fc_hasGroupingSets = false;
	Bitmapset  *fc_seen_local = NULL;

	/*
	 * 递归扁平化隐式的 RowExprs。 （从技术上讲，仅在根据语法规则
	 * 需要对分组集进行 GROUP BY，但我们还是这样做了。）
	 */
	fc_flat_grouplist = (List *) fc_flatten_grouping_sets((Node *) fc_grouplist,
													true,
													&fc_hasGroupingSets);

	/*
	 * 如果列表现在为空，但 hasGroupingSets 为真，这意味着我们
	 * 省略了多余的空分组集。恢复一个空的分组集以留下一个规范形式：
	 * GROUP BY ()
	 */

	if (fc_flat_grouplist == NIL && fc_hasGroupingSets)
	{
		fc_flat_grouplist = list_make1(makeGroupingSet(GROUPING_SET_EMPTY,
													NIL,
													exprLocation((Node *) fc_grouplist)));
	}

	foreach(fc_gl, fc_flat_grouplist)
	{
		Node	   *fc_gexpr = (Node *) lfirst(fc_gl);

		if (IsA(fc_gexpr, GroupingSet))
		{
			GroupingSet *fc_gset = (GroupingSet *) fc_gexpr;

			switch (fc_gset->kind)
			{
				case GROUPING_SET_EMPTY:
					fc_gsets = lappend(fc_gsets, fc_gset);
					break;
				case GROUPING_SET_SIMPLE:
					/* 不能发生 */
					Assert(false);
					break;
				case GROUPING_SET_SETS:
				case GROUPING_SET_CUBE:
				case GROUPING_SET_ROLLUP:
					fc_gsets = lappend(fc_gsets,
									fc_transformGroupingSet(&fc_result,
														 fc_pstate, fc_gset,
														 fc_targetlist, fc_sortClause,
														 fc_exprKind, fc_useSQL99, true));
					break;
			}
		}
		else
		{
			Index		fc_ref = fc_transformGroupClauseExpr(&fc_result, fc_seen_local,
													   fc_pstate, fc_gexpr,
													   fc_targetlist, fc_sortClause,
													   fc_exprKind, fc_useSQL99, true);

			if (fc_ref > 0)
			{
				fc_seen_local = bms_add_member(fc_seen_local, fc_ref);
				if (fc_hasGroupingSets)
					fc_gsets = lappend(fc_gsets,
									makeGroupingSet(GROUPING_SET_SIMPLE,
													list_make1_int(fc_ref),
													exprLocation(fc_gexpr)));
			}
		}
	}

	/* 解析器应该防止这种情况 */
	Assert(fc_gsets == NIL || fc_groupingSets != NULL);

	if (fc_groupingSets)
		*fc_groupingSets = fc_gsets;

	return fc_result;
}

/*
 * transformSortClause -
 *	  转换 ORDER BY 子句
 *
 * ORDER BY 项目将被添加到目标列表中（作为 resjunk 列）
 * 如果尚未存在，因此目标列表必须通过引用传递。
 *
 * 这也用于窗口和聚合 ORDER BY 子句（其作用
 * 几乎相同，但始终根据 SQL99 规则进行解释）。
 */
List * transformSortClause(ParseState *fc_pstate,
					List *fc_orderlist,
					List **fc_targetlist,
					ParseExprKind fc_exprKind,
					bool fc_useSQL99)
{
	List	   *fc_sortlist = NIL;
	ListCell   *fc_olitem;

	foreach(fc_olitem, fc_orderlist)
	{
		SortBy	   *fc_sortby = (SortBy *) lfirst(fc_olitem);
		TargetEntry *fc_tle;

		if (fc_useSQL99)
			fc_tle = fc_findTargetlistEntrySQL99(fc_pstate, fc_sortby->node,
										   fc_targetlist, fc_exprKind);
		else
			fc_tle = fc_findTargetlistEntrySQL92(fc_pstate, fc_sortby->node,
										   fc_targetlist, fc_exprKind);

		fc_sortlist = addTargetToSortList(fc_pstate, fc_tle,
									   fc_sortlist, *fc_targetlist, fc_sortby);
	}

	return fc_sortlist;
}

/*
 * transformWindowDefinitions -
 *		转换窗口定义 (WindowDef 到 WindowClause)
 */
List * transformWindowDefinitions(ParseState *fc_pstate,
						   List *fc_windowdefs,
						   List **fc_targetlist)
{
	List	   *fc_result = NIL;
	Index		fc_winref = 0;
	ListCell   *fc_lc;

	foreach(fc_lc, fc_windowdefs)
	{
		WindowDef  *fc_windef = (WindowDef *) lfirst(fc_lc);
		WindowClause *fc_refwc = NULL;
		List	   *fc_partitionClause;
		List	   *fc_orderClause;
		Oid			fc_rangeopfamily = InvalidOid;
		Oid			fc_rangeopcintype = InvalidOid;
		WindowClause *fc_wc;

		fc_winref++;

		/*
		 * 检查重复的窗口名称。
		 */
		if (fc_windef->name &&
			fc_findWindowClause(fc_result, fc_windef->name) != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_WINDOWING_ERROR),
					 errmsg("window \"%s\" is already defined", fc_windef->name),
					 parser_errposition(fc_pstate, fc_windef->location)));

		/*
		 * 如果引用了之前的窗口，则查找该窗口。
		 */
		if (fc_windef->refname)
		{
			fc_refwc = fc_findWindowClause(fc_result, fc_windef->refname);
			if (fc_refwc == NULL)
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_OBJECT),
						 errmsg("window \"%s\" does not exist",
								fc_windef->refname),
						 parser_errposition(fc_pstate, fc_windef->location)));
		}

		/*
		 * 转换PARTITION和ORDER规格（如果有的话）。这些被视为
		 * 与顶级GROUP BY和ORDER BY子句几乎完全相同，
		 * 包括对非默认操作符语义的特殊处理。
		 */
		fc_orderClause = transformSortClause(fc_pstate,
										  fc_windef->orderClause,
										  fc_targetlist,
										  EXPR_KIND_WINDOW_ORDER,
										  true /* 强制SQL99规则 */ );
		fc_partitionClause = transformGroupClause(fc_pstate,
											   fc_windef->partitionClause,
											   NULL,
											   fc_targetlist,
											   fc_orderClause,
											   EXPR_KIND_WINDOW_PARTITION,
											   true /* 强制SQL99规则 */ );

		/*
		 * 并准备新的WindowClause。
		 */
		fc_wc = makeNode(WindowClause);
		fc_wc->name = fc_windef->name;
		fc_wc->refname = fc_windef->refname;

		/*
		 * 根据规范，引用之前窗口的windowdef会复制
		 * 之前的分区子句（并且不能指定其自己的）。它可以
		 * 指定自己的排序子句，但前提是之前的没有
		 * 排序子句。它始终指定自己的框架子句，而之前的
		 * 不得有框架子句。是的，这些情况各自处理的方式很奇怪，
		 * 但SQL:2008就是这样规定的；见7.11
		 * <窗口子句>语法规则10和一般规则1。框架
		 * 子句规则特别奇怪，因为它使“OVER foo”
		 * 与“OVER (foo)”不同，并要求后者在foo有
		 * 非默认框架子句时抛出错误。嗯，我们不能质疑原因，
		 * 但我们确实努力为这种情况提供有用的错误
		 * 信息。
		 */
		if (fc_refwc)
		{
			if (fc_partitionClause)
				ereport(ERROR,
						(errcode(ERRCODE_WINDOWING_ERROR),
						 errmsg("cannot override PARTITION BY clause of window \"%s\"",
								fc_windef->refname),
						 parser_errposition(fc_pstate, fc_windef->location)));
			fc_wc->partitionClause = copyObject(fc_refwc->partitionClause);
		}
		else
			fc_wc->partitionClause = fc_partitionClause;
		if (fc_refwc)
		{
			if (fc_orderClause && fc_refwc->orderClause)
				ereport(ERROR,
						(errcode(ERRCODE_WINDOWING_ERROR),
						 errmsg("cannot override ORDER BY clause of window \"%s\"",
								fc_windef->refname),
						 parser_errposition(fc_pstate, fc_windef->location)));
			if (fc_orderClause)
			{
				fc_wc->orderClause = fc_orderClause;
				fc_wc->copiedOrder = false;
			}
			else
			{
				fc_wc->orderClause = copyObject(fc_refwc->orderClause);
				fc_wc->copiedOrder = true;
			}
		}
		else
		{
			fc_wc->orderClause = fc_orderClause;
			fc_wc->copiedOrder = false;
		}
		if (fc_refwc && fc_refwc->frameOptions != FRAMEOPTION_DEFAULTS)
		{
			/*
			 * 如果这是一个WINDOW子句，或者如果它是一个包含
			 * ORDER BY或框架子句的OVER子句，则使用此消息。
			 * （我们已经在上面拒绝了PARTITION BY，所以不需要检查。）
			 */
			if (fc_windef->name ||
				fc_orderClause || fc_windef->frameOptions != FRAMEOPTION_DEFAULTS)
				ereport(ERROR,
						(errcode(ERRCODE_WINDOWING_ERROR),
						 errmsg("cannot copy window \"%s\" because it has a frame clause",
								fc_windef->refname),
						 parser_errposition(fc_pstate, fc_windef->location)));
			/* 否则这个子句只是OVER (foo)，所以说： */
			ereport(ERROR,
					(errcode(ERRCODE_WINDOWING_ERROR),
					 errmsg("cannot copy window \"%s\" because it has a frame clause",
							fc_windef->refname),
					 errhint("Omit the parentheses in this OVER clause."),
					 parser_errposition(fc_pstate, fc_windef->location)));
		}
		fc_wc->frameOptions = fc_windef->frameOptions;

		/*
		 * RANGE偏移PRECEDING/FOLLOWING需要精确一个ORDER BY
		 * 列；检查一下，并获取其排序操作族信息。
		 */
		if ((fc_wc->frameOptions & FRAMEOPTION_RANGE) &&
			(fc_wc->frameOptions & (FRAMEOPTION_START_OFFSET |
								 FRAMEOPTION_END_OFFSET)))
		{
			SortGroupClause *fc_sortcl;
			Node	   *fc_sortkey;
			int16		fc_rangestrategy;

			if (list_length(fc_wc->orderClause) != 1)
				ereport(ERROR,
						(errcode(ERRCODE_WINDOWING_ERROR),
						 errmsg("RANGE with offset PRECEDING/FOLLOWING requires exactly one ORDER BY column"),
						 parser_errposition(fc_pstate, fc_windef->location)));
			fc_sortcl = linitial_node(SortGroupClause, fc_wc->orderClause);
			fc_sortkey = get_sortgroupclause_expr(fc_sortcl, *fc_targetlist);
			/* 在pg_amop中查找排序运算符 */
			if (!get_ordering_op_properties(fc_sortcl->sortop,
											&fc_rangeopfamily,
											&fc_rangeopcintype,
											&fc_rangestrategy))
				elog(ERROR, "operator %u is not a valid ordering operator",
					 fc_sortcl->sortop);
			/* 记录排序顺序的属性 */
			fc_wc->inRangeColl = exprCollation(fc_sortkey);
			fc_wc->inRangeAsc = (fc_rangestrategy == BTLessStrategyNumber);
			fc_wc->inRangeNullsFirst = fc_sortcl->nulls_first;
		}

		/* 根据规范，GROUPS模式需要一个ORDER BY子句 */
		if (fc_wc->frameOptions & FRAMEOPTION_GROUPS)
		{
			if (fc_wc->orderClause == NIL)
				ereport(ERROR,
						(errcode(ERRCODE_WINDOWING_ERROR),
						 errmsg("GROUPS mode requires an ORDER BY clause"),
						 parser_errposition(fc_pstate, fc_windef->location)));
		}

		/* 处理框架偏移表达式 */
		fc_wc->startOffset = fc_transformFrameOffset(fc_pstate, fc_wc->frameOptions,
											   fc_rangeopfamily, fc_rangeopcintype,
											   &fc_wc->startInRangeFunc,
											   fc_windef->startOffset);
		fc_wc->endOffset = fc_transformFrameOffset(fc_pstate, fc_wc->frameOptions,
											 fc_rangeopfamily, fc_rangeopcintype,
											 &fc_wc->endInRangeFunc,
											 fc_windef->endOffset);
		fc_wc->runCondition = NIL;
		fc_wc->winref = fc_winref;

		fc_result = lappend(fc_result, fc_wc);
	}

	return fc_result;
}

/*
 * transformDistinctClause -
 *	  转换DISTINCT子句
 *
 * 由于我们可能需要向查询的目标列表中添加项，因此该列表
 * 是通过引用传递的。
 *
 * 与GROUP BY一样，我们尽可能将ORDER BY的排序语义吸收到
 * distinctClause中。这避免了可能的重新排序需求，
 * 并允许用户选择用于DISTINCT的相等语义，
 * 如果她正在处理具有多个相等操作符的数据类型。
 *
 * is_agg为真，如果我们正在转换一个聚合（DISTINCT ...）
 * 函数调用。这不会影响任何行为，仅影响错误消息的措辞。
 */
List * transformDistinctClause(ParseState *fc_pstate,
						List **fc_targetlist, List *fc_sortClause, bool fc_is_agg)
{
	List	   *fc_result = NIL;
	ListCell   *fc_slitem;
	ListCell   *fc_tlitem;

	/*
	 * distinctClause应由所有ORDER BY项和所有
	 * 其他非resjunk目标列表项组成。不得有任何resjunk
	 * ORDER BY项——这将暗示我们按不一定在DISTINCT组内唯一的
	 * 值排序，因此结果将不够明确。此构造确保我们遵循
	 * sortClause和distinctClause匹配的规则；实际上，
	 * sortClause将始终是distinctClause的前缀。
	 *
	 * 注意一个边缘情况：同一个TLE可以在ORDER BY列表中多次
	 * 出现，具有不同的sortops。我们必须以相同的方式将其
	 * 包含在distinctClause中，以保持前缀属性。最终效果是
	 * TLE值将根据两种sortops变得唯一。
	 */
	foreach(fc_slitem, fc_sortClause)
	{
		SortGroupClause *fc_scl = (SortGroupClause *) lfirst(fc_slitem);
		TargetEntry *fc_tle = get_sortgroupclause_tle(fc_scl, *fc_targetlist);

		if (fc_tle->resjunk)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
					 fc_is_agg ?
					 errmsg("in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list") :
					 errmsg("for SELECT DISTINCT, ORDER BY expressions must appear in select list"),
					 parser_errposition(fc_pstate,
										exprLocation((Node *) fc_tle->expr))));
		fc_result = lappend(fc_result, copyObject(fc_scl));
	}

	/*
	 * 现在添加任何剩余的非resjunk tlist项，使用默认的
	 * 排序/分组语义。
	 */
	foreach(fc_tlitem, *fc_targetlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_tlitem);

		if (fc_tle->resjunk)
			continue;			/* 忽略垃圾 */
		fc_result = fc_addTargetToGroupList(fc_pstate, fc_tle,
									  fc_result, *fc_targetlist,
									  exprLocation((Node *) fc_tle->expr));
	}

	/*
	 * 如果我们发现没有东西可以使DISTINCT，则提出异议。
	 * 返回一个空列表将导致解析的查询看起来没有DISTINCT，生成
	 * 可能让用户感到惊讶的结果。注意：由于语法限制，
	 * 目前聚合的这种情况是不可能的，但我们还是检查一下。
	 */
	if (fc_result == NIL)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 fc_is_agg ?
				 errmsg("an aggregate with DISTINCT must have at least one argument") :
				 errmsg("SELECT DISTINCT must have at least one column")));

	return fc_result;
}

/*
 * transformDistinctOnClause -
 *	  转换DISTINCT ON子句
 *
 * 由于我们可能需要向查询的目标列表中添加项，因此该列表
 * 是通过引用传递的。
 *
 * 与GROUP BY一样，我们尽可能将ORDER BY的排序语义吸收到
 * distinctClause中。这避免了可能的重新排序需求，
 * 并允许用户选择用于DISTINCT的相等语义，
 * 如果她正在处理具有多个相等操作符的数据类型。
 */
List * transformDistinctOnClause(ParseState *fc_pstate, List *fc_distinctlist,
						  List **fc_targetlist, List *fc_sortClause)
{
	List	   *fc_result = NIL;
	List	   *fc_sortgrouprefs = NIL;
	bool		fc_skipped_sortitem;
	ListCell   *fc_lc;
	ListCell   *fc_lc2;

	/*
	 * 将所有DISTINCT ON表达式添加到tlist（如果尚未存在，
	 * 则作为resjunk项添加）。为它们分配sortgroupref编号，
	 * 并列出这些编号。（注意：我们在下面依赖于sortgrouprefs
	 * 列表与原始distinctlist一一对应。此外，请注意，我们可能
	 * 有重复的DISTINCT ON表达式，从而在sortgrouprefs中
	 * 导致重复条目。）
	 */
	foreach(fc_lc, fc_distinctlist)
	{
		Node	   *fc_dexpr = (Node *) lfirst(fc_lc);
		int			fc_sortgroupref;
		TargetEntry *fc_tle;

		fc_tle = fc_findTargetlistEntrySQL92(fc_pstate, fc_dexpr, fc_targetlist,
									   EXPR_KIND_DISTINCT_ON);
		fc_sortgroupref = assignSortGroupRef(fc_tle, *fc_targetlist);
		fc_sortgrouprefs = lappend_int(fc_sortgrouprefs, fc_sortgroupref);
	}

	
/*
	 * 如果用户同时写入 DISTINCT ON 和 ORDER BY，则采用
	 * 从与 DISTINCT ON 项匹配的 ORDER BY 项中获得的排序
	 * 语义，并且还要采纳它们的列排序顺序。我们坚持 distinctClause 和
	 * sortClause 必须匹配，因此如果发现需要添加任何更多的
	 * distinctClause 项，在我们跳过一个不在 DISTINCT ON 中的
	 * ORDER BY 项之后，就会抛出错误。
	 */
	fc_skipped_sortitem = false;
	foreach(fc_lc, fc_sortClause)
	{
		SortGroupClause *fc_scl = (SortGroupClause *) lfirst(fc_lc);

		if (list_member_int(fc_sortgrouprefs, fc_scl->tleSortGroupRef))
		{
			if (fc_skipped_sortitem)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
						 errmsg("SELECT DISTINCT ON expressions must match initial ORDER BY expressions"),
						 parser_errposition(fc_pstate,
											get_matching_location(fc_scl->tleSortGroupRef,
																  fc_sortgrouprefs,
																  fc_distinctlist))));
			else
				fc_result = lappend(fc_result, copyObject(fc_scl));
		}
		else
			fc_skipped_sortitem = true;
	}

	/*
	 * 现在添加任何剩余的 DISTINCT ON 项，使用其数据类型的默认排序/分组
	 * 语义。 （注意：这相当有争议；如果 ORDER BY 列表没有包含所有的
	 * DISTINCT ON 项以及更多内容，你肯定不是以预期的方式使用 DISTINCT ON，
	 * 而且你可能不会获得一致的结果。在这里抛出错误或警告可能更好。
	 * 但从历史上看，我们允许这样，因此继续这样做。）
	 */
	forboth(fc_lc, fc_distinctlist, fc_lc2, fc_sortgrouprefs)
	{
		Node	   *fc_dexpr = (Node *) lfirst(fc_lc);
		int			fc_sortgroupref = lfirst_int(fc_lc2);
		TargetEntry *fc_tle = get_sortgroupref_tle(fc_sortgroupref, *fc_targetlist);

		if (targetIsInSortList(fc_tle, InvalidOid, fc_result))
			continue;			/* 已在列表中（带有一些语义） */
		if (fc_skipped_sortitem)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
					 errmsg("SELECT DISTINCT ON expressions must match initial ORDER BY expressions"),
					 parser_errposition(fc_pstate, exprLocation(fc_dexpr))));
		fc_result = fc_addTargetToGroupList(fc_pstate, fc_tle,
									  fc_result, *fc_targetlist,
									  exprLocation(fc_dexpr));
	}

	/*
	 * 由于语法限制，这里不可能有空的结果列表。
	 */
	Assert(fc_result != NIL);

	return fc_result;
}

/*
 * get_matching_location
 *		获取与 sortgroupref 相等的 sortgrouprefs 中的
 *		(exprs) 成员对应的 exprLocation。
 *
 * 这用于指向一个麻烦的 DISTINCT ON 条目。
 * （请注意，我们需要使用原始的未转换的 DISTINCT ON 列表
 * 项，因为它对应的任何 TLE 很可能有一个解析位置指向
 * SELECT 列表或 ORDER BY 列表中的某个匹配条目。）
 */
static int get_matching_location(int fc_sortgroupref, List *fc_sortgrouprefs, List *fc_exprs)
{
	ListCell   *fc_lcs;
	ListCell   *fc_lce;

	forboth(fc_lcs, fc_sortgrouprefs, fc_lce, fc_exprs)
	{
		if (lfirst_int(fc_lcs) == fc_sortgroupref)
			return exprLocation((Node *) lfirst(fc_lce));
	}
	/* 如果没有匹配，调用者出错 */
	elog(ERROR, "get_matching_location: no matching sortgroupref");
	return -1;					/* 保持编译器安静 */
}

/*
 * resolve_unique_index_expr
 *		从 indexElems 列表推断唯一索引，用于 ON
 *		CONFLICT 子句
 *
 * 对在 ON CONFLICT 子句中出现的表达式和列进行解析分析。
 * 在规划期间，返回的表达式列表用于推断要使用哪个唯一索引。
 */
static List * fc_resolve_unique_index_expr(ParseState *fc_pstate, InferClause *fc_infer,
						  Relation fc_heapRel)
{
	List	   *fc_result = NIL;
	ListCell   *fc_l;

	foreach(fc_l, fc_infer->indexElems)
	{
		IndexElem  *fc_ielem = (IndexElem *) lfirst(fc_l);
		InferenceElem *fc_pInfer = makeNode(InferenceElem);
		Node	   *fc_parse;

		/*
		 * 原始语法重用 CREATE INDEX 基础设施用于唯一索引
		 * 推断子句，因此将按名称接受操作类等。
		 *
		 * 不要试图匹配 ASC 或 DESC 排序或 NULLS FIRST/NULLS
		 * LAST 排序，因为这些对于推断目的并不重要
		 * （任何与推断规范在其他方面匹配的唯一索引都是无差别地接受的）。
		 * 积极拒绝这个作为错误的想法。
		 */
		if (fc_ielem->ordering != SORTBY_DEFAULT)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
					 errmsg("ASC/DESC is not allowed in ON CONFLICT clause"),
					 parser_errposition(fc_pstate,
										exprLocation((Node *) fc_infer))));
		if (fc_ielem->nulls_ordering != SORTBY_NULLS_DEFAULT)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
					 errmsg("NULLS FIRST/LAST is not allowed in ON CONFLICT clause"),
					 parser_errposition(fc_pstate,
										exprLocation((Node *) fc_infer))));

		if (!fc_ielem->expr)
		{
			/* 简单索引属性 */
			ColumnRef  *fc_n;

			/*
			 * 在简单的列引用事件中，语法不会为我们构建原始表达式。
			 * 直接创建一个，并进行表达式转换。规划器期望这样，并进行
			 * 自身的规范化，以匹配 pg_index。
			 */
			fc_n = makeNode(ColumnRef);
			fc_n->fields = list_make1(makeString(fc_ielem->name));
			/* 位置大致是推断规范的位置 */
			fc_n->location = fc_infer->location;
			fc_parse = (Node *) fc_n;
		}
		else
		{
			/* 对原始表达式进行解析转换 */
			fc_parse = (Node *) fc_ielem->expr;
		}

		/*
		 * transformExpr() 将基于传递的
		 * EXPR_KIND_INDEX_EXPRESSION 拒绝子查询、聚合
		 * 函数、窗口函数和 SRFs。因此，我们无需担心这些
		 * 进一步的问题...即使它们也无法匹配任何可用的索引
		 * 表达式。
		 */
		fc_pInfer->expr = transformExpr(fc_pstate, fc_parse, EXPR_KIND_INDEX_EXPRESSION);

		/* 根据需要执行排序和运算符类的查找 */
		if (!fc_ielem->collation)
			fc_pInfer->infercollid = InvalidOid;
		else
			fc_pInfer->infercollid = LookupCollation(fc_pstate, fc_ielem->collation,
												  exprLocation(fc_pInfer->expr));

		if (!fc_ielem->opclass)
			fc_pInfer->inferopclass = InvalidOid;
		else
			fc_pInfer->inferopclass = get_opclass_oid(BTREE_AM_OID,
												   fc_ielem->opclass, false);

		fc_result = lappend(fc_result, fc_pInfer);
	}

	return fc_result;
}

/*
 * transformOnConflictArbiter -
 *		转换 ON CONFLICT 子句中的仲裁者表达式。
 *
 * 转换后的表达式用于推断一个唯一的索引关系作为
 * ON CONFLICT 仲裁者。可以使用推断规范子句中的 WHERE
 * 子句推断部分唯一索引。
 */
void transformOnConflictArbiter(ParseState *fc_pstate,
						   OnConflictClause *fc_onConflictClause,
						   List **fc_arbiterExpr, Node **fc_arbiterWhere,
						   Oid *fc_constraint)
{
	InferClause *fc_infer = fc_onConflictClause->infer;

	*fc_arbiterExpr = NIL;
	*fc_arbiterWhere = NULL;
	*fc_constraint = InvalidOid;

	if (fc_onConflictClause->action == ONCONFLICT_UPDATE && !fc_infer)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("ON CONFLICT DO UPDATE requires inference specification or constraint name"),
				 errhint("For example, ON CONFLICT (column_name)."),
				 parser_errposition(fc_pstate,
									exprLocation((Node *) fc_onConflictClause))));

	/*
	 * 为简化设计的某些方面，不允许对
	 * 系统目录进行推测性插入
	 */
	if (IsCatalogRelation(fc_pstate->p_target_relation))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("ON CONFLICT is not supported with system catalog tables"),
				 parser_errposition(fc_pstate,
									exprLocation((Node *) fc_onConflictClause))));

	/* 逻辑解码所使用的表同样适用于目录表 */
	if (RelationIsUsedAsCatalogTable(fc_pstate->p_target_relation))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("ON CONFLICT is not supported on table \"%s\" used as a catalog table",
						RelationGetRelationName(fc_pstate->p_target_relation)),
				 parser_errposition(fc_pstate,
									exprLocation((Node *) fc_onConflictClause))));

	/* ON CONFLICT DO NOTHING 不需要推断子句 */
	if (fc_infer)
	{
		if (fc_infer->indexElems)
			*fc_arbiterExpr = fc_resolve_unique_index_expr(fc_pstate, fc_infer,
													 fc_pstate->p_target_relation);

		/*
		 * 处理推断 WHERE 子句（用于部分唯一索引
		 * 推断）
		 */
		if (fc_infer->whereClause)
			*fc_arbiterWhere = transformExpr(fc_pstate, fc_infer->whereClause,
										  EXPR_KIND_INDEX_PREDICATE);

		/*
		 * 如果仲裁者是通过约束名称指定的，则获取约束
		 * OID，并将受约束列标记为需要 SELECT 权限，
		 * 这种方式与当仲裁者被明确引用其约束的索引列时
		 * 所发生的方式相同。
		 */
		if (fc_infer->conname)
		{
			Oid			fc_relid = RelationGetRelid(fc_pstate->p_target_relation);
			RangeTblEntry *fc_rte = fc_pstate->p_target_nsitem->p_rte;
			Bitmapset  *fc_conattnos;

			fc_conattnos = get_relation_constraint_attnos(fc_relid, fc_infer->conname,
													   false, fc_constraint);

			/* 确保整个关系被标记为可执行 SELECT 访问 */
			fc_rte->requiredPerms |= ACL_SELECT;
			/* 将受约束列标记为需要 SELECT 访问 */
			fc_rte->selectedCols = bms_add_members(fc_rte->selectedCols, fc_conattnos);
		}
	}

	/*
	 * 根据 CREATE INDEX 使用的表示形式形成表达式列表是方便的，
	 * 因为相同的限制是适用的（例如，对于子查询）。然而，从这里开始，
	 * 使用专门的 primnode 表示法用于推断元素，因此
	 * assign_query_collations() 可以被信任，能够对解析后分析查询树
	 * 的推断子句表示做出正确的处理。
	 */
}

/*
 * addTargetToSortList
 *		如果给定的目标列表条目不在 SortGroupClause
 *		列表中，使用给定的排序顺序信息将其添加到列表末尾。
 *
 * 返回更新后的 SortGroupClause 列表。
 */
List * addTargetToSortList(ParseState *fc_pstate, TargetEntry *fc_tle,
					List *fc_sortlist, List *fc_targetlist, SortBy *fc_sortby)
{
	Oid			fc_restype = exprType((Node *) fc_tle->expr);
	Oid			fc_sortop;
	Oid			fc_eqop;
	bool		fc_hashable;
	bool		fc_reverse;
	int			fc_location;
	ParseCallbackState fc_pcbstate;

	/* 如果 tlist 项是 UNKNOWN 字面量，则将其更改为 TEXT */
	if (fc_restype == UNKNOWNOID)
	{
		fc_tle->expr = (Expr *) coerce_type(fc_pstate, (Node *) fc_tle->expr,
										 fc_restype, TEXTOID, -1,
										 COERCION_IMPLICIT,
										 COERCE_IMPLICIT_CAST,
										 -1);
		fc_restype = TEXTOID;
	}

	/*
	 * 为了避免混淆 get_sort_group_operators 和我们即将使用的其他
	 * 函数的 API，利用错误上下文回调将任何错误报告标记
	 * 以解析位置。如果存在，则指向操作符位置，否则指向
	 * 正在排序的表达式。（注意：在这里使用原始未转换的
	 * 表达式；TLE 条目可能指向常规 SELECT 列表中的重复表达式。）
	 */
	fc_location = fc_sortby->location;
	if (fc_location < 0)
		fc_location = exprLocation(fc_sortby->node);
	setup_parser_errposition_callback(&fc_pcbstate, fc_pstate, fc_location);

	/* 确定 sortop、eqop 和方向性 */
	switch (fc_sortby->sortby_dir)
	{
		case SORTBY_DEFAULT:
		case SORTBY_ASC:
			get_sort_group_operators(fc_restype,
									 true, true, false,
									 &fc_sortop, &fc_eqop, NULL,
									 &fc_hashable);
			fc_reverse = false;
			break;
		case SORTBY_DESC:
			get_sort_group_operators(fc_restype,
									 false, true, true,
									 NULL, &fc_eqop, &fc_sortop,
									 &fc_hashable);
			fc_reverse = true;
			break;
		case SORTBY_USING:
			Assert(fc_sortby->useOp != NIL);
			fc_sortop = compatible_oper_opid(fc_sortby->useOp,
										  fc_restype,
										  fc_restype,
										  false);

			/*
			 * 验证它是有效的排序运算符，获取相应的
			 * 相等运算符，并判断是否将其视为
			 * ASC 或 DESC。
			 */
			fc_eqop = get_equality_op_for_ordering_op(fc_sortop, &fc_reverse);
			if (!OidIsValid(fc_eqop))
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("operator %s is not a valid ordering operator",
								strVal(llast(fc_sortby->useOp))),
						 errhint("Ordering operators must be \"<\" or \">\" members of btree operator families.")));

			/*
			 * 还要查看相等运算符是否可哈希。
			 */
			fc_hashable = op_hashjoinable(fc_eqop, fc_restype);
			break;
		default:
			elog(ERROR, "unrecognized sortby_dir: %d", fc_sortby->sortby_dir);
			fc_sortop = InvalidOid;	/* 保持编译器安静 */
			fc_eqop = InvalidOid;
			fc_hashable = false;
			fc_reverse = false;
			break;
	}

	cancel_parser_errposition_callback(&fc_pcbstate);

	/* 避免创建重复的排序列表条目 */
	if (!targetIsInSortList(fc_tle, fc_sortop, fc_sortlist))
	{
		SortGroupClause *fc_sortcl = makeNode(SortGroupClause);

		fc_sortcl->tleSortGroupRef = assignSortGroupRef(fc_tle, fc_targetlist);

		fc_sortcl->eqop = fc_eqop;
		fc_sortcl->sortop = fc_sortop;
		fc_sortcl->hashable = fc_hashable;

		switch (fc_sortby->sortby_nulls)
		{
			case SORTBY_NULLS_DEFAULT:
				/* NULLS FIRST 是 DESC 的默认值；ASC 则相反 */
				fc_sortcl->nulls_first = fc_reverse;
				break;
			case SORTBY_NULLS_FIRST:
				fc_sortcl->nulls_first = true;
				break;
			case SORTBY_NULLS_LAST:
				fc_sortcl->nulls_first = false;
				break;
			default:
				elog(ERROR, "unrecognized sortby_nulls: %d",
					 fc_sortby->sortby_nulls);
				break;
		}

		fc_sortlist = lappend(fc_sortlist, fc_sortcl);
	}

	return fc_sortlist;
}

/*
 * addTargetToGroupList
 *		如果给定的目标列表条目不在 SortGroupClause
 *		列表中，使用默认的排序/分组语义将其添加到列表末尾。
 *
 * 这与 addTargetToSortList 非常相似，除了我们允许只找到
 * 分组（相等）运算符的情况，并且 TLE 被视为“已经在列表中”，
 * 如果它以任何排序语义出现在那里。
 *
 * location 是在发生问题时需要标记的解析位置。
 * 注意，我们无法依赖 exprLocation(tle->expr)，因为这可能指向与
 * GROUP BY 项匹配的 SELECT 项；报告这样的位置信息可能会非常
 * 令人困惑。
 *
 * 返回更新后的 SortGroupClause 列表。
 */
static List * fc_addTargetToGroupList(ParseState *fc_pstate, TargetEntry *fc_tle,
					 List *fc_grouplist, List *fc_targetlist, int fc_location)
{
	Oid			fc_restype = exprType((Node *) fc_tle->expr);

	/* 如果 tlist 项是 UNKNOWN 字面量，则将其更改为 TEXT */
	if (fc_restype == UNKNOWNOID)
	{
		fc_tle->expr = (Expr *) coerce_type(fc_pstate, (Node *) fc_tle->expr,
										 fc_restype, TEXTOID, -1,
										 COERCION_IMPLICIT,
										 COERCE_IMPLICIT_CAST,
										 -1);
		fc_restype = TEXTOID;
	}

	/* 避免创建重复的分组列表条目 */
	if (!targetIsInSortList(fc_tle, InvalidOid, fc_grouplist))
	{
		SortGroupClause *fc_grpcl = makeNode(SortGroupClause);
		Oid			fc_sortop;
		Oid			fc_eqop;
		bool		fc_hashable;
		ParseCallbackState fc_pcbstate;

		setup_parser_errposition_callback(&fc_pcbstate, fc_pstate, fc_location);

		/* 确定 eqop 和可选的 sortop */
		get_sort_group_operators(fc_restype,
								 false, true, false,
								 &fc_sortop, &fc_eqop, NULL,
								 &fc_hashable);

		cancel_parser_errposition_callback(&fc_pcbstate);

		fc_grpcl->tleSortGroupRef = assignSortGroupRef(fc_tle, fc_targetlist);
		fc_grpcl->eqop = fc_eqop;
		fc_grpcl->sortop = fc_sortop;
		fc_grpcl->nulls_first = false; /* 有或没有 sortop 都可以 */
		fc_grpcl->hashable = fc_hashable;

		fc_grouplist = lappend(fc_grouplist, fc_grpcl);
	}

	return fc_grouplist;
}

/*
 * assignSortGroupRef
 *	  如果目标条目没有未使用的 ressortgroupref，分配一个。
 *	  返回分配的或已有的 refnumber。
 *
 * 'tlist' 是包含（或将包含）给定目标条目的目标列表。
 */
Index
assignSortGroupRef(TargetEntry *fc_tle, List *fc_tlist)
{
	Index		fc_maxRef;
	ListCell   *fc_l;

	if (fc_tle->ressortgroupref)	/* 已经有一个了吗？ */
		return fc_tle->ressortgroupref;

	/* 获取未使用的 refnumber 的最简单方法：最大使用 + 1 */
	fc_maxRef = 0;
	foreach(fc_l, fc_tlist)
	{
		Index		fc_ref = ((TargetEntry *) lfirst(fc_l))->ressortgroupref;

		if (fc_ref > fc_maxRef)
			fc_maxRef = fc_ref;
	}
	fc_tle->ressortgroupref = fc_maxRef + 1;
	return fc_tle->ressortgroupref;
}

/*
 * targetIsInSortList
 *		给定的目标项是否已在排序列表中？
 *		如果 sortop 不是 InvalidOid，还要测试与 sortop 的匹配。
 *
 * 这个函数忽略 nulls_first 标志并不是一个疏漏。
 * 我们在确定 ORDER BY 项是否与早期的 ORDER BY 项冗余时检查 sortop，
 * 因为可以想象 “ORDER BY
 * foo USING <, foo USING <<<” 是不冗余的，如果 <<< 区分了 < 认为相等的值。
 * 然而，我们不需要检查 nulls_first，
 * 因为具有相同 sortop 但相反 nulls 方向的较低排序列是冗余的。
 * 此外，我们可以认为 ORDER BY foo ASC, foo DESC 是冗余的，因此检查一个对称匹配。
 *
 * 适用于排序和分组列表（考虑分组时 sortop 通常是
 * InvalidOid）。请注意，我们需要这个例程的主要原因（而不仅仅是对 ressortgroupref 非零性的快速测试）
 * 是因为一个 TLE 可能只在其中一个列表中。
 */
bool targetIsInSortList(TargetEntry *fc_tle, Oid fc_sortop, List *fc_sortList)
{
	Index		fc_ref = fc_tle->ressortgroupref;
	ListCell   *fc_l;

	/* 如果 tle 没有标记，则无需扫描列表 */
	if (fc_ref == 0)
		return false;

	foreach(fc_l, fc_sortList)
	{
		SortGroupClause *fc_scl = (SortGroupClause *) lfirst(fc_l);

		if (fc_scl->tleSortGroupRef == fc_ref &&
			(fc_sortop == InvalidOid ||
			 fc_sortop == fc_scl->sortop ||
			 fc_sortop == get_commutator(fc_scl->sortop)))
			return true;
	}
	return false;
}

/*
 * findWindowClause
 *		在列表中查找命名的 WindowClause，如果不存在则返回 NULL
 */
static WindowClause * fc_findWindowClause(List *fc_wclist, const char *fc_name)
{
	ListCell   *fc_l;

	foreach(fc_l, fc_wclist)
	{
		WindowClause *fc_wc = (WindowClause *) lfirst(fc_l);

		if (fc_wc->name && strcmp(fc_wc->name, fc_name) == 0)
			return fc_wc;
	}

	return NULL;
}

/*
 * transformFrameOffset
 *		处理窗口框架偏移表达式
 *
 * 在 RANGE 模式下，rangeopfamily 是输入 ORDER BY
 * 列的排序操作族，而 rangeopcintype 是与排序运算符注册的输入数据类型。
 * 我们期望 in_range 函数也与该相同类型注册。
 * （在二进制兼容的情况下，它可能与输入列的实际类型不同，因此我们不能用它进行查找。）
 * 我们将返回 in_range 函数的 OID 到 *inRangeFunc。
 */
static Node * fc_transformFrameOffset(ParseState *fc_pstate, int fc_frameOptions,
					 Oid fc_rangeopfamily, Oid fc_rangeopcintype, Oid *fc_inRangeFunc,
					 Node *fc_clause)
{
	const char *fc_constructName = NULL;
	Node	   *fc_node;

	*fc_inRangeFunc = InvalidOid;	/* 默认结果 */

	/* 如果没有偏移表达式，则快速退出 */
	if (fc_clause == NULL)
		return NULL;

	if (fc_frameOptions & FRAMEOPTION_ROWS)
	{
		/* 转换原始表达式树 */
		fc_node = transformExpr(fc_pstate, fc_clause, EXPR_KIND_WINDOW_FRAME_ROWS);

		/*
		 * 如同 LIMIT 子句，简单地强制转换为 int8
		 */
		fc_constructName = "ROWS";
		fc_node = coerce_to_specific_type(fc_pstate, fc_node, INT8OID, fc_constructName);
	}
	else if (fc_frameOptions & FRAMEOPTION_RANGE)
	{
		/*
		 * 我们必须查找要使用的 in_range 支持函数，可能选择几个中的一个，并将“offset”值强制转换为适当的输入类型。
		 */
		Oid			fc_nodeType;
		Oid			fc_preferredType;
		int			fc_nfuncs = 0;
		int			fc_nmatches = 0;
		Oid			fc_selectedType = InvalidOid;
		Oid			fc_selectedFunc = InvalidOid;
		CatCList   *fc_proclist;
		int			fc_i;

		/* 转换原始表达式树 */
		fc_node = transformExpr(fc_pstate, fc_clause, EXPR_KIND_WINDOW_FRAME_RANGE);
		fc_nodeType = exprType(fc_node);

		/*
		 * 如果有多个候选者，我们会优先选择与 nodeType 完全匹配的那个；或者如果 nodeType 仍然未知，优先选择与排序列类型完全匹配的那个。 （第二条规则类似于我们对“已知类型运算符未知”的处理。）
		 */
		fc_preferredType = (fc_nodeType != UNKNOWNOID) ? fc_nodeType : fc_rangeopcintype;

		/* 找到适用于此情况的 in_range 支持函数 */
		fc_proclist = SearchSysCacheList2(AMPROCNUM,
									   ObjectIdGetDatum(fc_rangeopfamily),
									   ObjectIdGetDatum(fc_rangeopcintype));
		for (fc_i = 0; fc_i < fc_proclist->n_members; fc_i++)
		{
			HeapTuple	fc_proctup = &fc_proclist->members[fc_i]->tuple;
			Form_pg_amproc fc_procform = (Form_pg_amproc) GETSTRUCT(fc_proctup);

			/* 搜索将找到所有支持过程类型；忽略其他类型 */
			if (fc_procform->amprocnum != BTINRANGE_PROC)
				continue;
			fc_nfuncs++;

			/* 如果给定值无法强制转换为该类型，则忽略该函数 */
			if (!can_coerce_type(1, &fc_nodeType, &fc_procform->amprocrighttype,
								 COERCION_IMPLICIT))
				continue;
			fc_nmatches++;

			/* 记住首选匹配项，或如果没有找到则记住任何匹配项 */
			if (fc_selectedType != fc_preferredType)
			{
				fc_selectedType = fc_procform->amprocrighttype;
				fc_selectedFunc = fc_procform->amproc;
			}
		}
		ReleaseCatCacheList(fc_proclist);

		/*
		 * 如果需要，抛出错误。区分“完全没有支持”和“您没有匹配任何可用的偏移类型”似乎值得费心。
		 */
		if (fc_nfuncs == 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("RANGE with offset PRECEDING/FOLLOWING is not supported for column type %s",
							format_type_be(fc_rangeopcintype)),
					 parser_errposition(fc_pstate, exprLocation(fc_node))));
		if (fc_nmatches == 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("RANGE with offset PRECEDING/FOLLOWING is not supported for column type %s and offset type %s",
							format_type_be(fc_rangeopcintype),
							format_type_be(fc_nodeType)),
					 errhint("Cast the offset value to an appropriate type."),
					 parser_errposition(fc_pstate, exprLocation(fc_node))));
		if (fc_nmatches != 1 && fc_selectedType != fc_preferredType)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("RANGE with offset PRECEDING/FOLLOWING has multiple interpretations for column type %s and offset type %s",
							format_type_be(fc_rangeopcintype),
							format_type_be(fc_nodeType)),
					 errhint("Cast the offset value to the exact intended type."),
					 parser_errposition(fc_pstate, exprLocation(fc_node))));

		/* 好的，将 offset 强制转换为正确的类型 */
		fc_constructName = "RANGE";
		fc_node = coerce_to_specific_type(fc_pstate, fc_node,
									   fc_selectedType, fc_constructName);
		*fc_inRangeFunc = fc_selectedFunc;
	}
	else if (fc_frameOptions & FRAMEOPTION_GROUPS)
	{
		/* 转换原始表达式树 */
		fc_node = transformExpr(fc_pstate, fc_clause, EXPR_KIND_WINDOW_FRAME_GROUPS);

		/*
		 * 如同 LIMIT 子句，简单地强制转换为 int8
		 */
		fc_constructName = "GROUPS";
		fc_node = coerce_to_specific_type(fc_pstate, fc_node, INT8OID, fc_constructName);
	}
	else
	{
		Assert(false);
		fc_node = NULL;
	}

	/* 不允许在帧偏移中使用变量 */
	fc_checkExprIsVarFree(fc_pstate, fc_node, fc_constructName);

	return fc_node;
}
